简介
说明
本文用实例介绍Spring的BeanPostProcessor的应用。
所有的Bean都会走到BeanPostProcessor接口的postProcessBeforeInitialization和postProcessAfterInitialization方法。
Bean的生命周期流程见:Spring-Bean生命周期-流程/原理 – 自学精灵
简单示例
代码
package com.example.processor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class TestProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization==> " + "bean:" + bean + "; " + "beanName:" + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization==> " + "bean:" + bean + "; " + "beanName:" + beanName); return bean; } }
运行结果(太长,只贴一部分)
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.0.RELEASE) 2021-03-04 23:40:59.246 INFO 5820 --- [ main] com.example.DemoApplication : Starting DemoApplication on DESKTOP-QI6B9ME with PID 5820 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot) 2021-03-04 23:40:59.248 INFO 5820 --- [ main] com.example.DemoApplication : No active profile set, falling back to default profiles: default postProcessBeforeInitialization==> bean:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat@1136b469; beanName:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat postProcessAfterInitialization==> bean:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat@1136b469; beanName:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat postProcessBeforeInitialization==> bean:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory@512d4583; beanName:tomcatServletWebServerFactory postProcessBeforeInitialization==> bean:org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration@2abc224d; beanName:org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration postProcessAfterInitialization==> bean:org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration@2abc224d; beanName:org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration postProcessBeforeInitialization==> bean:org.springframework.boot.autoconfigure.websocket.servlet.TomcatWebSocketServletWebServerCustomizer@2b97cc1f; beanName:websocketServletWebServerCustomizer postProcessAfterInitialization==> bean:org.springframework.boot.autoconfigure.websocket.servlet.TomcatWebSocketServletWebServerCustomizer@2b97cc1f; beanName:websocketServletWebServerCustomizer postProcessBeforeInitialization==> bean:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration@64f555e7; beanName:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration postProcessAfterInitialization==> bean:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration@64f555e7; beanName:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
高级应用:注入属性
简介
Spring开发时,会遇到同一个接口有多个实现类,通常有如下几种方法:
- 通过@Autowired+@Qualify引入接口:@Autowired @Qualify(“helloServiceImpl2”) private HelloService helloService;
- 通过@Autowired+@Primary:在某个实现类上标注@Primary
- 在具体调用的地方通过ApplicationContext根据业务的需要来选择不同的接口实现类,虽然可以在抽象出一个工厂方法,但是还是感觉不够优雅。
其实上边这些方法已经能满足开发需求了,而且推荐使用法1。本处,使用BeanPostProcessor+自定义注解来达到同样的作用。当然,这么写是重复造轮子,本质上是毫无意义的。但是,我们可以从本例子中学到如下几点:
- 给使用了自定义注解的属性赋值的方法
- 代理工厂类的用法
- BeanPostProcessor的用法
接口
package com.example.service; public interface HelloService { void sayHello(); }
实现类
package com.example.service.impl; import com.example.service.HelloService; import org.springframework.stereotype.Service; @Service public class HelloServiceImpl1 implements HelloService { @Override public void sayHello() { System.out.println("我是HelloServiceImpl1"); } }
package com.example.service.impl; import com.example.service.HelloService; import org.springframework.stereotype.Service; @Service public class HelloServiceImpl2 implements HelloService { @Override public void sayHello() { System.out.println("我是HelloServiceImpl2"); } }
自定义注解
package com.example.annotation; import org.springframework.stereotype.Component; import java.lang.annotation.*; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface RountingInjected { String value() default "helloServiceImpl1"; }
自定义BeanPostProcessor实现类
package com.example.processor; import com.example.annotation.RountingInjected; import com.example.factory.RoutingBeanProxyFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.Map; @Component public class HelloServiceInjectProcessor implements BeanPostProcessor { @Autowired private ApplicationContext applicationContext; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> targetCls = bean.getClass(); Field[] targetFld = targetCls.getDeclaredFields(); for (Field field : targetFld) { //找到指定目标的注解类 if (field.isAnnotationPresent(RountingInjected.class)) { if (!field.getType().isInterface()) { throw new BeanCreationException("RoutingInjected field must be declared as an interface:" + field.getName() + " @Class " + targetCls.getName()); } try { this.handleRoutingInjected(field, bean, field.getType()); } catch (IllegalAccessException e) { e.printStackTrace(); } } } return bean; } private void handleRoutingInjected(Field field, Object bean, Class type) throws IllegalAccessException { Map<String, Object> candidates = this.applicationContext.getBeansOfType(type); field.setAccessible(true); if (candidates.size() == 1) { field.set(bean, candidates.values().iterator().next()); } else if (candidates.size() == 2) { String injectVal = field.getAnnotation(RountingInjected.class).value(); Object proxy = RoutingBeanProxyFactory.createProxy(injectVal, type, candidates); field.set(bean, proxy); } else { throw new IllegalArgumentException("Find more than 2 beans for type: " + type); } } }
代理实现类
package com.example.factory; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.ProxyFactory; import java.util.Map; public class RoutingBeanProxyFactory { private final static String DEFAULT_BEAN_NAME = "helloServiceImpl1"; public static Object createProxy(String name, Class type, Map<String, Object> candidates) { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setInterfaces(type); proxyFactory.addAdvice(new VersionRoutingMethodInterceptor(name, candidates)); return proxyFactory.getProxy(); } static class VersionRoutingMethodInterceptor implements MethodInterceptor { private Object targetObject; public VersionRoutingMethodInterceptor(String name, Map<String, Object> beans) { this.targetObject = beans.get(name); if (this.targetObject == null) { this.targetObject = beans.get(DEFAULT_BEAN_NAME); } } @Override public Object invoke(MethodInvocation invocation) throws Throwable { return invocation.getMethod().invoke(this.targetObject, invocation.getArguments()); } } }
测试类
package com.example.controller; import com.example.annotation.RountingInjected; import com.example.service.HelloService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RountingInjected(value = "helloServiceImpl2") private HelloService helloService; @GetMapping("/test1") public String test1() { helloService.sayHello(); return "test1 success"; } }
测试
访问:http://localhost:8080/test1
后端结果:
我是HelloServiceImpl2
请先
!