• Spring Boot 如何实现Bean的刷新(转)


    参考:https://blog.csdn.net/WXF_Sir/article/details/124603774

    https://www.jianshu.com/p/36a065065e3c

    1. 从无到有 —— 了解Bean容器
    首先得了解Scope(作用域),简单介绍两种。

    1. singleton 单例类型
    整个程序运行期间,Spring容器只会有一个对应类的Bean实例,不管被加载到哪里使用,都是一个实例对象。
    曾经,我犯过一个错。将公用返回对象以默认的方式加载至Spring容器(笑)

    2. prototype 原型类型
    和单例相反,这种加载模式,Bean实例不管在哪里被获取,都不是同一个对象。
    刚好可以解决上面,公用返回对象的问题(笑)(笑)

    2. 如何实现 —— 建立自己的作用域
    毕竟面向浏览器编程的我,也不是白吹的。

    浏览器启动 -> spring bean 自定义作用域 -> 点击 -> 浏览 -> 原来如此 -> 点赞 -> 再见

    等等,我看的啥来着?

    // 注册一个名叫 “refresh” 的bean作用域,处理对象是 RefreshScope的实例

    @SpringBootApplication
    public class Application {
    
    
        public static void main(String[] args) {
            // 注册一个名叫 “refresh” 的bean作用域,处理对象是 RefreshScope的实例
            ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
            applicationContext.getBeanFactory().registerScope("refresh", refreshScope());
        }
    
        @Bean
        public static RefreshScope refreshScope(){
            return new RefreshScope();
        }
    }


    RefreshScope类需要实现Scope接口

    (1)get(String name, ObjectFactory<?> objectFactory)方法 通过名字,获取bean的实例对象。 关于第二参数:objectFactory,大致意思就是目标类构造工厂

    OK了。

    3. 如何实现 —— 使用refresh作用域
    // 创建Refresh注解

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Scope("refresh")
    @Documented
    public @interface Refresh {
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
    }


    我们需要Bean配置变化的时候进行刷新。 所以应该让Spring把Bean的获取操作交由我们自己处理(上面的get方法)。 再利用原型加载prototype类似的机制,让Bean每次都调用get方法。

    @Configuration
    public class Test {
        private static final AtomicLong seed=new AtomicLong(1L);
        @Bean
        @Refresh
        public User a(){
            return new User(seed.getAndIncrement());
        }
    }

    user的实现

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Long id;
    }


    可以试着在@Autowired加载A实例的地方,打个断点,看看A对象的类型。

    加载的A实例并不是A实例本身,通过添加@Refresh注解后,ScopedProxyMode.TARGET_CLASS参数(使用CGLIB代理),让A的注入方式变为代理注入。 注入的是一个代理对象,实现懒加载,在需要调用的时候,进行对象获取(就是上述的get方法)

    4. 如何实现 —— 回到我们的作用域 RefreshScope类
    public class RefreshScope implements Scope {
    // 省略***

    @Slf4j
    public class RefreshScope implements Scope {
        // 省略***
        private static final String TARGET = "scopedTarget.";
        private static final Map<String, Object> map = new ConcurrentHashMap<>();
    
        @Override
        public Object get(String name, ObjectFactory<?> objectFactory) {
                 return   objectFactory.getObject();
        }
    
       
    
        @Override
        public Object remove(String name) {
            return null;
        }
    
        @Override
        public void registerDestructionCallback(String name, Runnable callback) {
    
        }
    
        @Override
        public Object resolveContextualObject(String key) {
            return null;
        }
    
        @Override
        public String getConversationId() {
            return null;
        }
    
        // 省略***
    }



    // 省略***
    }
    现在每次使用添加l @Refresh注解的Bean时,都会调用get方法。

    如同原型加载一样,每次都生成新的对象不就成功刷新了嘛?

    缓存是个好东西呀,没有发生变化的时候,重新生成新对象,不是很浪费嘛。

    @Slf4j
    public class RefreshScope implements Scope {
        // 省略***
        private static final String TARGET = "scopedTarget.";
        private static final Map<String, Object> map = new ConcurrentHashMap<>();
    
        @Override
        public Object get(String name, ObjectFactory<?> objectFactory) {
            Object bean = map.get(name);
            // 加个缓存判断
            return bean == null ? map.put(name, objectFactory.getObject()) : bean;
    //      return   objectFactory.getObject();
        }
    
        // 自定义一个缓存失效方法,很奇怪的是这个地方的beanName是方法名a
        public void refresh(String name) {
            map.remove(TARGET + name);
        }
    
        @Override
        public Object remove(String name) {
            return null;
        }
    
        @Override
        public void registerDestructionCallback(String name, Runnable callback) {
    
        }
    
        @Override
        public Object resolveContextualObject(String key) {
            return null;
        }
    
        @Override
        public String getConversationId() {
            return null;
        }
    
        // 省略***
    }



    // 省略***
    }
    5. 完成
    要进行Bean刷新,调用RefreshScope的refresh()方法就行了。

    比如MongoTemplate对象。

    @Bean
    @Refresh
    public MongoTemplate mongoTemplate() {
    return new MongoTemplate(new MongoClient(MongoConfig.getHost(), MongoConfig.getPort()), MongoConfig.getDbName());
    }
    当MongoConfig对象参数发生变化。

    //使缓存失效。
    refreshScope.refresh('mongoTemplate')
    关于refreshScope实例,可以交给Spring容器管理,也可以自己管理。

    当缓存失效后,使用到mongoTemplate时,将会再次调用mongoTemplate()方法重新生成,并缓存MongoTemplate实例。
    ————————————————
    版权声明:本文为CSDN博主「BUG指挥官」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/WXF_Sir/article/details/124603774

  • 相关阅读:
    自定义注解 使用反射实现切面编程
    JPA学习(基于hibernate)
    MySql数据库优化可以从哪几个方面进行?
    mybatis注解开发-动态SQL
    mybatis注解开发
    mybatis动态sql
    Java8新特性之forEach遍历
    mybatis一对多映射
    CentOS7.* 查询开机启动项
    CentOS7 升级openssh到openssh-8.0p1版本
  • 原文地址:https://www.cnblogs.com/wangbin2188/p/16524514.html
Copyright © 2020-2023  润新知