首先得了解Scope
(作用域),简单介绍两种。
1. singleton 单例类型
整个程序运行期间,Spring
容器只会有一个对应类的Bean
实例,不管被加载到哪里使用,都是一个实例对象。
曾经,我犯过一个错。将公用返回对象以默认的方式加载至Spring
容器(笑)
2. prototype 原型类型
和单例相反,这种加载模式,Bean
实例不管在哪里被获取,都不是同一个对象。
刚好可以解决上面,公用返回对象的问题(笑)(笑)
毕竟面向浏览器编程的我,也不是白吹的。
浏览器启动 -> spring bean 自定义作用域 -> 点击 -> 浏览 -> 原来如此 ->
点赞
-> 再见
等等,我看的啥来着?
// 注册一个名叫 “refresh” 的bean作用域,处理对象是 RefreshScope的实例
applicationContext.getBeanFactory().registerScope("refresh", refreshScope);
RefreshScope
类需要实现Scope
接口
(1)
get(String name, ObjectFactory> objectFactory)
方法 通过名字,获取bean的实例对象。 关于第二参数:objectFactory
,大致意思就是目标类构造工厂
OK了。
// 创建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
方法。
@Bean
@Refresh
public A a(){
return new A();
}
可以试着在@Autowired
加载A
实例的地方,打个断点,看看A对象的类型。
加载的A实例并不是A实例本身,通过添加
@Refresh
注解后,ScopedProxyMode.TARGET_CLASS
参数(使用CGLIB代理),让A的注入方式变为代理注入。 注入的是一个代理对象,实现懒加载,在需要调用的时候,进行对象获取(就是上述的get方法)
public class RefreshScope implements Scope {
// 省略***
@Override
public Object get(String name, ObjectFactory> objectFactory) {
//TODO
}
// 省略***
}
现在每次使用添加l @Refresh
注解的Bean时,都会调用get方法。
如同原型加载一样,每次都生成新的对象不就成功刷新了嘛?
public class RefreshScope implements Scope {
// 省略***
@Override
public Object get(String name, ObjectFactory> objectFactory) {
return objectFactory.getObject();
}
// 省略***
}
显然是可以的,每次都会再去执行一遍a()方法。就可以在这里获取最新配置,然后重新生成A实例。
@Bean
@Refresh
public A a(){
return new A();
}
NO!!!!!!
缓存是个好东西呀,没有发生变化的时候,重新生成新对象,不是很浪费嘛。
public class RefreshScope implements Scope {
// 省略***
// get方法过来的name有前缀
private static final String TARGET = "scopedTarget.";
// 自己定义的一个缓存类,可以自己选择存取方式进行实现,需要考虑线程安全问题
private BeanCache beanCache = new BeanCache();
@Override
public Object get(String name, ObjectFactory> objectFactory) {
Object bean = beanCache.get(name);
// 加个缓存判断
return bean==null?beanCache.put(name, objectFactory.getObject()):bean;
}
// 自定义一个缓存失效方法
public void refresh()(String name) {
beanCache.remove(TARGET + name);
}
// 省略***
}
要进行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实例。