文章目录
- 简单介绍Spring流程
- 从问题入手介绍扩展点
- 如何在static方法中从Spring容器中获取bean对象
- 如何在bean实例化前后做一些自定义操作
- 如何移除,修改一些BeanDefination
- 怎样用相同的线程从Spring容器中获取同一个bean对象
- 如何在Spring容器初始化或销毁时做一些自定义操作
- 如何利用Spring内部的Listenr机制,实现自定义事件发布通知
简单介绍Spring流程
在介绍自定义扩展点之前,首先大概回顾一下Spring的IOC流程,主要做了哪些事, 理清楚这个对我们实现自定义扩展点非常有帮助,理解更加透彻。
主要有以下几点:
- 从不同配置文件加载配置并解析
- 扫描包路径并结合配置形成BeanDefination, 这里面存储了bean的相关属性
- 根据BeanDefination中的信息实例化对象,将对象存储到Spring容器,即BeanFactory中
- 进行对象和容器的销毁
在Spring中有很多的PostProcessor(后置处理器),主要作用就是增强器,自定义扩展实现
还有很多的Aware,顾名思义就是知道的,可获取的,比如说BeanNameAware,实现这个接口我们可以获取BeanName, ApplicationContextAware实现这个接口,我们可以获取ApplicationContext
而Spring中的自定义扩展点主要是针对这两类接口实现的,当然还有其他的操作,我们一起结合案例来看一下。
从问题入手介绍扩展点
如何在static方法中从Spring容器中获取bean对象
在Spring中,如果一个方法是static方法, 是无法在方法中使用@Resource或者@Autowired注解注入的对象的
如下:会直接编译报错。
那怎样做呢,我们可以封装一个BeanUtil,该类实现ApplicationContextAware,获取到ApplicationContext,然后就可以从中获取对象。如下:
@Service
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 通过名称获取bean
* @param name
* @return
*/
public static Object getBean(String name) {
if(StringUtils.isEmpty(name)){
return null;
}
return applicationContext.getBean(name);
}
/**
* 通过类型获取bean
* @param requiredType
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> requiredType) {
if(requiredType == null){
return null;
}
return applicationContext.getBean(requiredType);
}
}
使用也很简单,直接调用方法即可:
举一反三:
前面已经提到以Aware结尾的接口,我们可以据此获取到对应的对象,从而做一些参数获取或其他操作,这里再总结一些其他的。
-
EnvironmentAware:用于获取Enviroment的一个扩展类,这个变量非常有用, 可以获得系统内的所有参数。包括配置文件中我们自定义的配置参数。
-
EmbeddedValueResolverAware:用于获取StringValueResolver的一个扩展类, StringValueResolver用于获取基于String类型的properties的变量,一般我们都用@Value的方式去获取,如果实现了这个Aware接口,把StringValueResolver缓存起来,通过这个类去获取String类型的变量,效果是一样的。
-
ResourceLoaderAware:用于获取ResourceLoader的一个扩展类,ResourceLoader可以用于获取classpath内所有的资源对象,可以扩展此类来拿到ResourceLoader对象。
-
ApplicationEventPublisherAware:用于获取ApplicationEventPublisher的一个扩展类,ApplicationEventPublisher可以用来发布事件,结合ApplicationListener来共同使用
-
ApplicationContextAware:用来获取ApplicationContext的一个扩展类,ApplicationContext应该是很多人非常熟悉的一个类了,就是spring上下文管理器,可以手动的获取任何在spring上下文注册的bean,我们经常扩展这个接口来缓存spring上下文,包装成静态方法。同时ApplicationContext也实现了BeanFactory,MessageSource,ApplicationEventPublisher等接口,也可以用来做相关接口的事情。
如何在bean实例化前后做一些自定义操作
我们可以实现BeanPostProcessor这个接口,重写以下两个方法。这两个方法都在bean实例化,属性赋值之后执行。
区别是一个在执行初始化方法之前执行,一个执行初始化方法之后执行。
注意事项:
这两个方法默认重写会返回null,但是一定不能返回null, 当不想操作修改某些对象时,直接返回bean即可。
案例:
在项目中自定义了一个注解, 这个注解标识在一些类上, 那我怎么识别到哪些类上有这个注解,从而做一些特殊操作呢?
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SelfDefine {
String value() default "";
}
一个是直接使用反射框架,扫描某个路径下的所有类,循环判断即可,但是比较麻烦,更简单的我们可以实现BeanPostProcessor,重写方法, 在方法中对每个bean判断即可。
@Service
public class Test implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
SelfDefine selfDefine = bean.getClass().getAnnotation(SelfDefine.class);
if(selfDefine!=null){
//实现一些自定义逻辑
String value = selfDefine.value();
System.out.println(value);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
如何移除,修改一些BeanDefination
在Spring中, 基本上所有的bean都是根据BeanDefination生成的,这里面存储了每个bean的相关属性,比如说作用域,接口等等。
所以如果我们想修改某个bean,可以直接修改BeanDefination. 甚至比如说删除, 不注入某个bean。
具体我们可以通过实现BeanDefinitionRegistryPostProcessor接口来操作。
如下案例:
在项目中的一个maven依赖中向Spring容器注入了某个bean, 但其实当前项目并不需要这个bean, 而且这个bean还需要额外增加一些配置,否则启动会报错。 所以在当前项目中需要移除该bean, 不让其报错。
@Service
public class Test implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//移除beanName为service
registry.removeBeanDefinition("service");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
怎样用相同的线程从Spring容器中获取同一个bean对象
我们都知道spring默认支持的Scope只有两种:
singleton 单例,每次从spring容器中获取到的bean都是同一个对象。
prototype 多例,每次从spring容器中获取到的bean都是不同的对象。
但是有些情况我们需要相同的线程获取到同一个对象,进行对象的复用,而不同的线程因为线程安全问题需要获取不同的对象,这个怎么实现呢?此时需要自定义Scope了。
- 实现Scope接口:
public class ThreadScope implements Scope {
private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal();
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object value = THREAD_LOCAL_SCOPE.get();
if (value != null) {
return value;
}
Object object = objectFactory.getObject();
THREAD_LOCAL_SCOPE.set(object);
return object;
}
@Override
public Object remove(String name) {
THREAD_LOCAL_SCOPE.remove();
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
- 将新定义的Scope注入到spring容器中:
@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("threadScope", new ThreadScope());
}
}
- 使用新定义的Scope:
@Scope("threadScope")
@Service
public class Test {
public void test() {
}
}
注意:
以上说的作用域无论是单例,多例,还是相同线程共享一个对象都指的是从容器中获取的时候,这里很容易产生误解。
举个例子,比如一个对象你定义成多例的
@Scope("prototype")
@Component
@Slf4j
public class Service {
}
然后在Controller中,每次用请求获取该service对象,这个service每次会是不一样的吗?
@RestController
@Slf4j
public class Controller {
@Resource
Service service;
@GetMapping("/test2")
public String test(){
log.info("运行线程:{}",Thread.currentThread().getName());
return service.toString();
}
}
答案是no, 每次都是一样的,因为对于Controller这个对象来说,它是单例的,所以对于service这个属性也只从Spring容器中获取了一次。自然就都是一样的。
那怎么处理呢?
- 可以将Controller也搞成多例的,这样就会从容器中获取多次,达到效果。
- 可以用本篇文章最开始提到的自己封装的BeanUtil,每次使用的时候直接从Spring容器中获取,也可以达到效果。
如何在Spring容器初始化或销毁时做一些自定义操作
有时候,我们需要在spring容器初始化或关闭前,做一些额外的工作,比如:关闭资源文件等。
我们可以实现InitializingBean接口,来进行一些初始化工作
可以实现DisposableBean接口,来进行一些关闭资源文件等等工作
@Service
public class Test implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
//进行一些初始化操作
System.out.println("Spring容器已启动......");
}
@Override
public void destroy() throws Exception {
//进行一些关闭资源文件等操作
System.out.println("Spring容器准备关闭......");
}
}
如何利用Spring内部的Listenr机制,实现自定义事件发布通知
要实现自定义事件发布通知,我们可以利用ApplicationListener
主要有三步:
- 增加事件,继承ApplicationEvent类
public class TestEvent extends ApplicationEvent {
private String name;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public TestEvent(Object source,String name) {
super(source);
this.name = name;
}
public void test(){
System.out.println(name);
}
}
- 增加监听者,实现ApplicationListener接口
@Component
public class TestListener implements ApplicationListener<TestEvent>{
@Override
public void onApplicationEvent(TestEvent event) {
//执行相应事件的一些逻辑
event.test();
}
}
- 发布事件通知
@RestController
@Slf4j
public class Controller {
@Resource
ApplicationContext applicationContext;
@GetMapping("/test2")
public void test(){
//执行一些本身的逻辑
//......
//发布事件,也就是当执行到此处的时候,监听者可以监听到变化,从而执行对应逻辑
applicationContext.publishEvent(new TestEvent(this,"test"));
}
}
这个其实就典型的运用了观察者模式,只不过做了一个小升级,额外定义封装了Event接口,其实也就是为了区分通知的不同行为,这个做法可以让我们明确监听某个事件。
因为在Spring中,还有很多的内部通知、事件,比如
-
ContextRefreshedEvent
ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。 -
ContextStartedEvent
当使用ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。 -
ContextStoppedEvent
当使用ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作 -
ContextClosedEvent
当使用ConfigurableApplicationContext接口中的 close()方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启 -
RequestHandledEvent
这是一个web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件
如果我们自己想监听这些事件,也非常简单,只需要增加一个监听者就好了,如下:
@Component
public class TestRequestHandledEvent implements ApplicationListener<RequestHandledEvent> {
@Override
public void onApplicationEvent(RequestHandledEvent event) {
System.out.println("一个请求处理完毕......");
}
}
今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.