最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

揭秘SpringBoot!一分鐘教你實(shí)現(xiàn)配置的動(dòng)態(tài)神刷新

 更新時(shí)間:2024年03月08日 08:39:37   作者:無(wú)心六神通  
在今天的指南中,我們將深入探索SpringBoot?動(dòng)態(tài)刷新的強(qiáng)大功能,讓你的應(yīng)用保持最新鮮的狀態(tài),想象一下,無(wú)需重啟,你的應(yīng)用就能實(shí)時(shí)更新配置,是不是很酷?跟我一起,讓我們揭開這項(xiàng)技術(shù)如何讓開發(fā)變得更加靈活和高效的秘密吧!

關(guān)于SpringBoot的自定義配置源、配置刷新之前也介紹過(guò)幾篇博文;最近正好在使用apollo時(shí),排查配置未動(dòng)態(tài)刷新的問題時(shí),看了下它的具體實(shí)現(xiàn)發(fā)現(xiàn)挺有意思的;

接下來(lái)我們致敬經(jīng)典,看一下如果讓我們來(lái)實(shí)現(xiàn)配置的動(dòng)態(tài)刷新,應(yīng)該怎么搞?

# I. 配置使用姿勢(shì)

既然要支持配置的動(dòng)態(tài)刷新,那么我們就得先看一下,在SpringBoot中,常見的配置使用姿勢(shì)有哪些

# 1. @Value注解綁定

直接通過(guò)@Value注解,將一個(gè)對(duì)象得成員變量與Environment中的配置進(jìn)行綁定,如

@Slf4j

@RestController

public class IndexController

@Value("${config.type:-1}")

private Integer type;

@Value("${config.wechat:默認(rèn)}")

private String wechat;


private String email;


@Value("${config.email:default@email}")

public IndexController setEmail(String email) {

this.email = email;

return this;

}

}

注意:@Value支持SpEL

# 2. @ConfigurationProperties綁定

通過(guò)@ConfigurationProperties注解聲明一個(gè)配置類,這個(gè)類中的成員變量都是從Environment中進(jìn)行初始化

如:

@ConfigurationProperties(prefix = "config")

public class MyConfig {


private String user;


private String pwd;


private Integer type;

}

# 3. Environment.getProperty()直接獲取配置

直接從上下文中獲取配置,也常見于各種使用場(chǎng)景中,如

environment.getProperty("config.user");

# II. 配置刷新

接下來(lái)我們看一下,如何實(shí)現(xiàn)配置刷新后,上面的三種使用姿勢(shì)都能獲取到刷新后的值

# 1. 自定義一個(gè)屬性配置源

自定義一個(gè)配置源,我們直接基于內(nèi)存的ConcurrentHashMap來(lái)進(jìn)行模擬,內(nèi)部提供了一個(gè)配置更新的方法,當(dāng)配置刷新之后,還會(huì)對(duì)外廣播一個(gè)配置變更事件

public class SelfConfigContext {

private static volatile SelfConfigContext instance = new SelfConfigContext();


public static SelfConfigContext getInstance() {

return instance;

}


private Map<String, Object> cache = new ConcurrentHashMap<>();


public Map<String, Object> getCache() {

return cache;

}


private SelfConfigContext() {

// 將內(nèi)存的配置信息設(shè)置為最高優(yōu)先級(jí)

cache.put("config.type", 33);

cache.put("config.wechat", "一灰灰blog");

cache.put("config.github", "liuyueyi");

}



/**

* 更新配置

*

* @param key

* @param val

*/

public void updateConfig(String key, Object val) {

cache.put(key, val);

ConfigChangeListener.publishConfigChangeEvent(key);

}

}



/**

* 主要實(shí)現(xiàn)配置變更事件發(fā)布于監(jiān)聽

*/

@Component

public class ConfigChangeListener implements ApplicationListener<ConfigChangeListener.ConfigChangeEvent> {


@Override

public void onApplicationEvent(ConfigChangeEvent configChangeEvent) {

SpringValueRegistry.updateValue(configChangeEvent.getKey());

}


public static void publishConfigChangeEvent(String key) {

SpringUtil.getApplicationContext().publishEvent(new ConfigChangeEvent(Thread.currentThread().getStackTrace()[0], key));

}


@Getter

public static class ConfigChangeEvent extends ApplicationEvent {

private String key;


public ConfigChangeEvent(Object source, String key) {

super(source);

this.key = key;

}

}

}

接下來(lái)就需要將這個(gè)自定義的配置元,注冊(cè)到 environment 上下文,在這里我們可以借助ApplicationContextInitializer來(lái)實(shí)現(xiàn),在上下文初始化前,完成自定義配置注冊(cè)

public class SelfConfigContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

@Override

public void initialize(ConfigurableApplicationContext configurableApplicationContext) {

System.out.println("postProcessEnvironment#initialize");

ConfigurableEnvironment env = configurableApplicationContext.getEnvironment();

initialize(env);

}


protected void initialize(ConfigurableEnvironment environment) {

if (environment.getPropertySources().contains("selfSource")) {

// 已經(jīng)初始化過(guò)了,直接忽略

return;

}


MapPropertySource propertySource = new MapPropertySource("selfSource", SelfConfigContext.getInstance().getCache());

environment.getPropertySources().addFirst(propertySource);

}

}

接下來(lái)注冊(cè)這個(gè)擴(kuò)展點(diǎn),直接選擇在項(xiàng)目啟動(dòng)時(shí),進(jìn)行注冊(cè)

@SpringBootApplication

public class Application {

public static void main(String[] args) {

SpringApplication springApplication = new SpringApplication(Application.class);

springApplication.addInitializers(new SelfConfigContextInitializer());

springApplication.run(args);

}

}

# 2. Environment配置刷新

envionment實(shí)時(shí)獲取配置的方式,支持配置刷新應(yīng)該相對(duì)簡(jiǎn)單,如直接吐出一個(gè)接口,支持更新我們自定義配置源的配置,不做任何變更,這個(gè)配置應(yīng)該時(shí)同時(shí)更新的

首先提供一個(gè)Spring的工具類,用于更簡(jiǎn)單的獲取Spring上下文

@Component

public class SpringUtil implements ApplicationContextAware, EnvironmentAware {

private static ApplicationContext applicationContext;

private static Environment environment;


private static Binder binder;


@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

SpringUtil.applicationContext = applicationContext;

}


@Override

public void setEnvironment(Environment environment) {

SpringUtil.environment = environment;

binder = Binder.get(environment);

}


public static ApplicationContext getApplicationContext() {

return applicationContext;

}


public static Environment getEnvironment() {

return environment;

}


public static Binder getBinder() {

return binder;

}

}

配置更新的示例

@Slf4j

@RestController

public class IndexController {

@GetMapping(path = "update")

public String updateCache(String key, String val) {

SelfConfigContext.getInstance().updateConfig(key, val);

return "ok";

}


@GetMapping(path = "get")

public String getProperty(String key) {

return SpringUtil.getEnvironment().getProperty(key);

}

}

執(zhí)行驗(yàn)證一下:

# 3. @ConfigurationProperties 配置刷新

之前在介紹自定義屬性配置綁定時(shí)介紹過(guò),通過(guò)Binder來(lái)實(shí)現(xiàn)綁定配置的Config對(duì)象動(dòng)態(tài)刷新,我們這里同樣可以實(shí)現(xiàn)配置變更時(shí),主動(dòng)刷新@ConfigurationProperties注解綁定的屬性

具體實(shí)現(xiàn)如下,

@Slf4j
@Component
public class ConfigAutoRefresher implements ApplicationRunner {
    private Binder binder;
 
    /**
     * 配置變更之后的刷新
     */
    @EventListener()
    public void refreshConfig(ConfigChangeListener.ConfigChangeEvent event) {
        log.info("配置發(fā)生變更,開始動(dòng)態(tài)刷新: {}", event);
        SpringUtil.getApplicationContext().getBeansWithAnnotation(ConfigurationProperties.class).values().forEach(bean -> {
            Bindable<?> target = Bindable.ofInstance(bean).withAnnotations(AnnotationUtils.findAnnotation(bean.getClass(), ConfigurationProperties.class));
            bind(target);
        });
    }
 
    /**
     * 重新綁定bean對(duì)象對(duì)應(yīng)的配置值
     *
     * @param bindable
     * @param <T>
     */
    public <T> void bind(Bindable<T> bindable) {
        ConfigurationProperties propertiesAno = bindable.getAnnotation(ConfigurationProperties.class);
        if (propertiesAno != null) {
            BindHandler bindHandler = getBindHandler(propertiesAno);
            this.binder.bind(propertiesAno.prefix(), bindable, bindHandler);
        }
    }
 
    private BindHandler getBindHandler(ConfigurationProperties annotation) {
        BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
        if (annotation.ignoreInvalidFields()) {
            handler = new IgnoreErrorsBindHandler(handler);
        }
        if (!annotation.ignoreUnknownFields()) {
            UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
            handler = new NoUnboundElementsBindHandler(handler, filter);
        }
        return handler;
    }
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("初始化!");
        ConfigurableEnvironment environment = (ConfigurableEnvironment) SpringUtil.getEnvironment();
        this.binder = new Binder(ConfigurationPropertySources.from(environment.getPropertySources()),
                new PropertySourcesPlaceholdersResolver(environment),
                new DefaultConversionService(),
                ((ConfigurableApplicationContext) SpringUtil.getApplicationContext())
                        .getBeanFactory()::copyRegisteredEditorsTo);
    }
}

注意上面的實(shí)現(xiàn),分三類:

  • public <T> void bind(Bindable<T> bindable): 具體實(shí)現(xiàn)綁定配置刷新的邏輯

核心思想就是將當(dāng)前對(duì)象與environment配置進(jìn)行重新綁定

  • public void run: binder初始化

在應(yīng)用啟動(dòng)之后進(jìn)行回調(diào),確保是在environment準(zhǔn)備完畢之后回調(diào),獲取用于屬性配置綁定的binder,避免出現(xiàn)envionment還沒有準(zhǔn)備好

也可以借助實(shí)現(xiàn)EnvironmentPostProcessor來(lái)實(shí)現(xiàn)

  • public void refreshConfig(ConfigChangeListener.ConfigChangeEvent event): 配置刷新

通過(guò)@EventListener監(jiān)聽配置變更事件,找到所有的ConfigurationProperties修飾對(duì)象,執(zhí)行重新綁定邏輯

接下來(lái)我們驗(yàn)證一下配置變更是否會(huì)生效

@Data
@Component
@ConfigurationProperties(prefix = "config")
public class UserConfig {
    private String user;
 
    private String pwd;
 
    private Integer type;
 
    private String wechat;
}
 
 
@Slf4j
@RestController
public class IndexController {
    @Autowired
    private UserConfig userConfig;
    @GetMapping(path = "/user")
    public UserConfig user() {
        return userConfig;
    }
 
    @GetMapping(path = "update")
    public String updateCache(String key, String val) {
        selfConfigContainer.refreshConfig(key, val);
        SelfConfigContext.getInstance().updateConfig(key, val);
        return JSON.toJSONString(userConfig);
    }
}

定義一個(gè)UserConfig來(lái)接收config前綴開始的配置,通過(guò)update接口來(lái)更新相關(guān)配置,更新完畢之后返回UserConfig的結(jié)果

# 4. @Value 配置刷新

最后我們?cè)賮?lái)看一下@Value注解綁定的配置的刷新策略

其核心思想就是找出所有@Value綁定的成員變量,當(dāng)監(jiān)聽到配置變更之后,通過(guò)反射的方式進(jìn)行刷新

關(guān)鍵的實(shí)現(xiàn)如下

/**
 * 配置變更注冊(cè), 找到 @Value 注解修飾的配置,注冊(cè)到 SpringValueRegistry,實(shí)現(xiàn)統(tǒng)一的配置變更自動(dòng)刷新管理
 *
 * @author YiHui
 * @date 2023/6/26
 */
@Slf4j
@Component
public class SpringValueProcessor implements BeanPostProcessor {
    private final PlaceholderHelper placeholderHelper;
 
    public SpringValueProcessor() {
        this.placeholderHelper = new PlaceholderHelper();
    }
 
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class clazz = bean.getClass();
        for (Field field : findAllField(clazz)) {
            processField(bean, beanName, field);
        }
        for (Method method : findAllMethod(clazz)) {
            processMethod(bean, beanName, method);
        }
        return bean;
    }
 
    private List<Field> findAllField(Class clazz) {
        final List<Field> res = new LinkedList<>();
        ReflectionUtils.doWithFields(clazz, res::add);
        return res;
    }
 
    private List<Method> findAllMethod(Class clazz) {
        final List<Method> res = new LinkedList<>();
        ReflectionUtils.doWithMethods(clazz, res::add);
        return res;
    }
 
    /**
     * 成員變量上添加 @Value 方式綁定的配置
     *
     * @param bean
     * @param beanName
     * @param field
     */
    protected void processField(Object bean, String beanName, Field field) {
        // register @Value on field
        Value value = field.getAnnotation(Value.class);
        if (value == null) {
            return;
        }
        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
 
        if (keys.isEmpty()) {
            return;
        }
 
        for (String key : keys) {
            SpringValueRegistry.SpringValue springValue = new SpringValueRegistry.SpringValue(key, value.value(), bean, beanName, field);
            SpringValueRegistry.register(key, springValue);
            log.debug("Monitoring {}", springValue);
        }
    }
 
    /**
     * 通過(guò) @Value 修飾方法的方式,通過(guò)一個(gè)傳參進(jìn)行實(shí)現(xiàn)的配置綁定
     *
     * @param bean
     * @param beanName
     * @param method
     */
    protected void processMethod(Object bean, String beanName, Method method) {
        //register @Value on method
        Value value = method.getAnnotation(Value.class);
        if (value == null) {
            return;
        }
        //skip Configuration bean methods
        if (method.getAnnotation(Bean.class) != null) {
            return;
        }
        if (method.getParameterTypes().length != 1) {
            log.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
            return;
        }
 
        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
 
        if (keys.isEmpty()) {
            return;
        }
 
        for (String key : keys) {
            SpringValueRegistry.SpringValue springValue = new SpringValueRegistry.SpringValue(key, value.value(), bean, beanName, method);
            SpringValueRegistry.register(key, springValue);
            log.info("Monitoring {}", springValue);
        }
    }
}

上面的實(shí)現(xiàn),主要利用到BeanPostProcessor,在bean初始化之后,掃描當(dāng)前bean中是否有@Value綁定的屬性,若有,則注冊(cè)到自定義的SpringValueRegistry

注意事項(xiàng):

  • @Value有兩種綁定姿勢(shì),直接放在成員變量上,以及通過(guò)方法進(jìn)行注入

所以上面的實(shí)現(xiàn)策略中,有FieldMethod兩種不同的處理策略;

  • @Value支持SpEL表達(dá)式,我們需要對(duì)配置key進(jìn)行解析

相關(guān)的源碼,推薦直接在下面的項(xiàng)目中進(jìn)行獲取,demo中的實(shí)現(xiàn)也是來(lái)自apollo-client

接下來(lái)再看一下注冊(cè)配置綁定的實(shí)現(xiàn),核心方法比較簡(jiǎn)單,兩個(gè),一個(gè)注冊(cè),一個(gè)刷新

@Slf4j
public class SpringValueRegistry {
    public static Map<String, Set<SpringValue>> registry = new ConcurrentHashMap<>();
 
    /**
     * 像registry中注冊(cè)配置key綁定的對(duì)象W
     *
     * @param key
     * @param val
     */
    public static void register(String key, SpringValue val) {
        if (!registry.containsKey(key)) {
            synchronized (SpringValueRegistry.class) {
                if (!registry.containsKey(key)) {
                    registry.put(key, new HashSet<>());
                }
            }
        }
 
        Set<SpringValue> set = registry.getOrDefault(key, new HashSet<>());
        set.add(val);
    }
 
    /**
     * key對(duì)應(yīng)的配置發(fā)生了變更,找到綁定這個(gè)配置的屬性,進(jìn)行反射刷新
     *
     * @param key
     */
    public static void updateValue(String key) {
        Set<SpringValue> set = registry.getOrDefault(key, new HashSet<>());
        set.forEach(s -> {
            try {
                s.update((s1, aClass) -> SpringUtil.getBinder().bindOrCreate(s1, aClass));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }
 
 
    @Data
    public static class SpringValue {
        /**
         * 適合用于:配置是通過(guò)set類方法實(shí)現(xiàn)注入綁定的方式,只有一個(gè)傳參,為對(duì)應(yīng)的配置key
         */
        private MethodParameter methodParameter;
        /**
         * 成員變量
         */
        private Field field;
        /**
         * bean示例的弱引用
         */
        private WeakReference<Object> beanRef;
        /**
         * Spring Bean Name
         */
        private String beanName;
        /**
         * 配置對(duì)應(yīng)的key: 如 config.user
         */
        private String key;
        /**
         * 配置引用,如 ${config.user}
         */
        private String placeholder;
        /**
         * 配置綁定的目標(biāo)類型
         */
        private Class<?> targetType;
 
        public SpringValue(String key, String placeholder, Object bean, String beanName, Field field) {
            this.beanRef = new WeakReference<>(bean);
            this.beanName = beanName;
            this.field = field;
            this.key = key;
            this.placeholder = placeholder;
            this.targetType = field.getType();
        }
 
        public SpringValue(String key, String placeholder, Object bean, String beanName, Method method) {
            this.beanRef = new WeakReference<>(bean);
            this.beanName = beanName;
            this.methodParameter = new MethodParameter(method, 0);
            this.key = key;
            this.placeholder = placeholder;
            Class<?>[] paramTps = method.getParameterTypes();
            this.targetType = paramTps[0];
        }
 
        /**
         * 配置基于反射的動(dòng)態(tài)變更
         *
         * @param newVal String: 配置對(duì)應(yīng)的key   Class: 配置綁定的成員/方法參數(shù)類型, Object 新的配置值
         * @throws Exception
         */
        public void update(BiFunction<String, Class, Object> newVal) throws Exception {
            if (isField()) {
                injectField(newVal);
            } else {
                injectMethod(newVal);
            }
        }
 
        private void injectField(BiFunction<String, Class, Object> newVal) throws Exception {
            Object bean = beanRef.get();
            if (bean == null) {
                return;
            }
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            field.set(bean, newVal.apply(key, field.getType()));
            field.setAccessible(accessible);
            log.info("更新value: {}#{} = {}", beanName, field.getName(), field.get(bean));
        }
 
        private void injectMethod(BiFunction<String, Class, Object> newVal)
                throws Exception {
            Object bean = beanRef.get();
            if (bean == null) {
                return;
            }
            Object va = newVal.apply(key, methodParameter.getParameterType());
            methodParameter.getMethod().invoke(bean, va);
            log.info("更新method: {}#{} = {}", beanName, methodParameter.getMethod().getName(), va);
        }
 
        public boolean isField() {
            return this.field != null;
        }
    }
}

SpringValue的構(gòu)建,主要就是基于反射需要使用到的一些關(guān)鍵信息的組成上;可以按需進(jìn)行設(shè)計(jì)補(bǔ)充

到此,關(guān)于@Value注解的配置動(dòng)態(tài)刷新就已經(jīng)實(shí)現(xiàn)了,接下來(lái)寫幾個(gè)demo驗(yàn)證一下

@Slf4j
@RestController
public class IndexController {
    @Value("${config.type:-1}")
    private Integer type;
    @Value("${config.wechat:默認(rèn)}")
    private String wechat;
 
    private String email;
 
    @Value("${config.email:default@email}")
    public IndexController setEmail(String email) {
        this.email = email;
        return this;
    }
 
 
    @GetMapping(path = "update")
    public String updateCache(String key, String val) {
        SelfConfigContext.getInstance().updateConfig(key, val);
        return wechat + "_" + type + "_" + email;
    }
}

# 5. 小結(jié)

本文主要介紹了項(xiàng)目中配置的動(dòng)態(tài)刷新的實(shí)現(xiàn)方案,也可以看作是apollo配置中心的簡(jiǎn)易實(shí)現(xiàn)原理,其中涉及到的知識(shí)點(diǎn)較多,下面做一個(gè)簡(jiǎn)單的小結(jié)

  • 配置的三種使用姿勢(shì)
  • @Value綁定
  • @ConfigurationProperties綁定對(duì)象
  • environment.getProperty()
  • 自定義配置源加載
  • environment.getPropertySources().addFirst(MapPropertySource)
  • 配置刷新
  • Binder實(shí)現(xiàn)ConfigurationProperties刷新
  • 反射實(shí)現(xiàn)@Value注解刷新

到此這篇關(guān)于揭秘SpringBoot!一分鐘教你實(shí)現(xiàn)配置的動(dòng)態(tài)神刷新的文章就介紹到這了,更多相關(guān)SpringBoot 動(dòng)態(tài)刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于ObjectUtils.isEmpty()?和?null?的區(qū)別

    關(guān)于ObjectUtils.isEmpty()?和?null?的區(qū)別

    這篇文章主要介紹了關(guān)于ObjectUtils.isEmpty()?和?null?的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Mybatis-Plus批量插入用法詳解

    Mybatis-Plus批量插入用法詳解

    mybatis-plus的IService接口默認(rèn)提供saveBatch批量插入,也是唯一一個(gè)默認(rèn)批量插入,在數(shù)據(jù)量不是很大的情況下可以直接使用,但這種是一條一條執(zhí)行的效率上會(huì)有一定的瓶頸,今天我們就來(lái)研究研究mybatis-plus中的批量插入
    2023-02-02
  • Mybatis分步查詢的實(shí)現(xiàn)示例

    Mybatis分步查詢的實(shí)現(xiàn)示例

    本文主要介紹了Mybatis分步查詢的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Java畢業(yè)設(shè)計(jì)之多用戶宿舍管理系統(tǒng)的實(shí)現(xiàn)

    Java畢業(yè)設(shè)計(jì)之多用戶宿舍管理系統(tǒng)的實(shí)現(xiàn)

    這篇文章主要介紹了基于Java實(shí)現(xiàn)的多用戶宿舍管理系統(tǒng),本文采用了jsp、servlet、jdbc等技術(shù),文中示例代碼講解詳細(xì),需要的可以參考一下
    2022-02-02
  • 詳述IntelliJ IDEA 中自動(dòng)生成 serialVersionUID 的方法(圖文)

    詳述IntelliJ IDEA 中自動(dòng)生成 serialVersionUID 的方法(圖文)

    本篇文章主要介紹了詳述IntelliJ IDEA 中自動(dòng)生成 serialVersionUID 的方法(圖文),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-11-11
  • Java算法之快速排序舉例詳解

    Java算法之快速排序舉例詳解

    這篇文章主要介紹了Java算法之快速排序的相關(guān)資料,快速排序是一種高效的排序算法,通過(guò)遞歸的方式將待排序數(shù)組分成小部分進(jìn)行排序,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-04-04
  • @RequestBody不能映射到對(duì)象的解決

    @RequestBody不能映射到對(duì)象的解決

    這篇文章主要介紹了@RequestBody不能映射到對(duì)象的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 淺談Java鎖的膨脹過(guò)程以及一致性哈希對(duì)鎖膨脹的影響

    淺談Java鎖的膨脹過(guò)程以及一致性哈希對(duì)鎖膨脹的影響

    本文主要介紹了Java鎖的膨脹過(guò)程以及一致性哈希對(duì)鎖膨脹的影響,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • java編譯命令和啟動(dòng)命令的使用方式

    java編譯命令和啟動(dòng)命令的使用方式

    Java開發(fā)中,編譯源文件需使用javac命令,該命令能將.java文件編譯成.class字節(jié)碼文件,后者可在JVM上運(yùn)行,常用編譯選項(xiàng)包括-d指定輸出目錄,-classpath設(shè)置類搜索路徑等,啟動(dòng)Java程序使用java命令,它加載并運(yùn)行包含main方法的類
    2024-10-10
  • java底層JDK?Logging日志模塊處理細(xì)節(jié)深入分析

    java底層JDK?Logging日志模塊處理細(xì)節(jié)深入分析

    這篇文章主要為大家介紹了java底層JDK?Logging日志模塊處理細(xì)節(jié)深入分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03

最新評(píng)論

宁安市| 牟定县| 沾化县| 文安县| 通化县| 安平县| 鸡泽县| 亚东县| 陇南市| 河曲县| 疏附县| 丰宁| 土默特左旗| 神池县| 长阳| 中方县| 得荣县| 延安市| 绵阳市| 界首市| 岗巴县| 平湖市| 高陵县| 蓬安县| 黄浦区| 射洪县| 五台县| 邵阳市| 中宁县| 奉节县| 讷河市| 咸丰县| 泗阳县| 湘潭县| 阿鲁科尔沁旗| 铜山县| 衡山县| 临潭县| 玉龙| 新巴尔虎右旗| 增城市|