坑1:依赖的bean不走BeanPostProcessor
其他网址
谈谈Spring中的BeanPostProcessor接口 - 特务依昂 - 博客园
简介
在BeanPostProcessor的实现类中,依赖了其他的bean,那么被依赖的bean被创建时,将不会执行它所在的BeanPostProcessor实现类实现的方法。
因为由于BeanPostProcessor的实现类依赖于其他bean,所以这个bean需要在PostBean之前创建完成,这也就意味着在这个bean创建时,BeanPostProcessor的实现类还未初始化完成,所以不会调用它的方法。
验证
BeanPostProcessor实现类
package com.example.processor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class MyProcessor implements BeanPostProcessor { @Autowired MyBean myBean; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof MyBean) { System.out.println("postProcessBeforeInitialization==> " + "This is MyBean"); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof MyBean) { System.out.println("postProcessAfterInitialization==> " + "This is MyBean"); } return bean; } }
被依赖的Bean
package com.example.processor; import org.springframework.stereotype.Component; @Component public class MyBean { }
测试
启动后,没有相关打印。
若将@Autowired MyBean myBean删掉,则启动之后,会打印出:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.0.RELEASE) 2021-03-05 21:39:47.861 INFO 16100 --- [ main] com.example.DemoApplication : Starting DemoApplication on DESKTOP-QI6B9ME with PID 16100 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot) 2021-03-05 21:39:47.864 INFO 16100 --- [ main] com.example.DemoApplication : No active profile set, falling back to default profiles: default 2021-03-05 21:39:48.645 INFO 16100 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2021-03-05 21:39:48.652 INFO 16100 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2021-03-05 21:39:48.652 INFO 16100 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.35] 2021-03-05 21:39:48.730 INFO 16100 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2021-03-05 21:39:48.731 INFO 16100 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 825 ms postProcessBeforeInitialization==> This is MyBean postProcessAfterInitialization==> This is MyBean 2021-03-05 21:39:48.865 INFO 16100 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2021-03-05 21:39:48.998 INFO 16100 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021-03-05 21:39:49.007 INFO 16100 --- [ main] com.example.DemoApplication : Started DemoApplication in 1.557 seconds (JVM running for 2.638)
坑2:无法使用AOP
其他网址
谈谈Spring中的BeanPostProcessor接口 - 特务依昂 - 博客
BeanPostProcessor中autowired导致AOP失效 | 大专栏BeanPostProcessor启动时对依赖Bean的“误伤”陷阱(is not eligible for getting processed by all...)_YourBatman-CSDN博客
记一次Spring配置事故 - 飞昂之雪 - 博客园
业务类无法被AOP代理问题
原因
简述
BeanPostProcessor以及依赖的bean很有可能无法使用AOP。
此时,会有类似这样的打印信息:trationDelegate$BeanPostProcessorChecker : Bean 'myBean' of type [com.example.processor.MyBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
源码位置
这个是由BeanPostProcessorChecker类打印出来的,在源码中搜索它,发现此类是PostProcessorRegistrationDelegate的静态内部类。
@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) && this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) { if (logger.isInfoEnabled()) { logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() + "] is not eligible for getting processed by all BeanPostProcessors " + "(for example: not eligible for auto-proxying)"); } } return bean; }
打印此日志的原因
若注册到beanFactory的BeanPostProcessor的数量少于总的BeanPostProcessor的数量。即:有其他后置处理器还没准备好,这个bean(此处是myBean)就被实例化了。这个bean被实例化的原因一般是:BeanPostProcessor的实现类引用了这个bean,导致这个bean的实例化。
Spring官方文档的原话:
BeanPostProcessor instances and AOP auto-proxying
Classes that implement the BeanPostProcessor interface are special and are treated differently by the container. All BeanPostProcessor instances and beans that they directly reference are instantiated on startup, as part of the special startup phase of the ApplicationContext. Next, all BeanPostProcessor instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessor instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.
For any such bean, you should see an informational log message: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).
翻译:
实现了BeanPostProcessor 接口的类是特殊的,它们会被容器不同地对待。所有的BeanPostProcessor 实例和它们直接引用的beans在启动时被实例化,它们作为ApplicationContext的特殊启动阶段的一部分。然后,所有的BeanPostProcessor 实例都被以一个排序方式注册而且被应用到下一步的beans中。因为Spring的AOP自动代理是通过实现BeanPostProcessor接口来做的,所以BeanPostProcessor的实现类和它们直接引用的bean不满足AOP自动代理的条件,因此,它们无法被织入到AOP中。
对于这些bean,你会看到一些INFO级别的信息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).
实例
复现
BeanPostProcessor实现类
刻意实现Ordered接口,模拟MyProcessor的初始化早于其他BeanPostProcessor实现类的情况,否则,不易复现。本处我测试时,将其指定为最高优先级或者最低优先级,效果是一样的。优先级见:Bean生命周期--BeanPostProcessor综述_feiying0canglang的博客-CSDN博客
另外,此处必须实现Ordered接口才能指明顺序,如果使用@Order是无效的。猜测:@Order也是通过实现BeanPostProcessor接口来做的,所以,也无效。package com.example.processor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; @Component public class MyProcessor implements BeanPostProcessor, Ordered { @Autowired private MyBean myBean; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof MyBean) { System.out.println("postProcessBeforeInitialization==> " + "This is MyBean"); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof MyBean) { System.out.println("postProcessAfterInitialization==> " + "This is MyBean"); } return bean; } // 此方法用来测试AOP,作为切点 public void testAOP() { System.out.println("testAOP方法(MyProcessor)"); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } }
Bean
package com.example.processor; import org.springframework.stereotype.Component; @Component public class MyBean { // 此方法用来测试AOP,用作切点 public void testAOP() { System.out.println("testAOP方法(MyBean)"); } }
切面
package com.example.processor; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class BeanPostProcessorAspect { // 此方法织入PostBean的testAOP方法 @Before("execution(* com.example.processor.MyProcessor.testAOP(..))") public void before() { System.out.println("before MyProcessor#testAOP"); } // 此方法织入MyBean的testAOP方法 @Before("execution(* com.example.processor.MyBean.testAOP(..))") public void before2() { System.out.println("before MyBean#testAOP"); } }
ApplicationContextHolder工具类
package com.example.processor; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext context; public void setApplicationContext(ApplicationContext context) throws BeansException { ApplicationContextHolder.context = context; } public static ApplicationContext getContext() { return context; } }
测试类
package com.example.controller; import com.example.processor.MyBean; import com.example.processor.MyProcessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired MyProcessor myProcessor; @Autowired MyBean myBean; @GetMapping("/test1") public String test1() { myProcessor.testAOP(); myBean.testAOP(); return "test1 success"; } // @GetMapping("/test1") // public String test1() { // MyProcessor myProcessor = ApplicationContextHolder.getContext().getBean(MyProcessor.class); // myProcessor.testAOP(); // MyBean myBean = ApplicationContextHolder.getContext().getBean(MyBean.class); // myBean.testAOP(); // return "test1 success"; // } }
测试
1.复现无法AOP的现象
启动:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.0.RELEASE) 2021-03-06 12:23:36.622 INFO 14368 --- [ main] com.example.DemoApplication : Starting DemoApplication on DESKTOP-QI6B9ME with PID 14368 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot) 2021-03-06 12:23:36.625 INFO 14368 --- [ main] com.example.DemoApplication : No active profile set, falling back to default profiles: default 2021-03-06 12:23:37.237 INFO 14368 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'myBean' of type [com.example.processor.MyBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2021-03-06 12:23:37.492 INFO 14368 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2021-03-06 12:23:37.500 INFO 14368 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2021-03-06 12:23:37.500 INFO 14368 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.35] 2021-03-06 12:23:37.590 INFO 14368 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2021-03-06 12:23:37.590 INFO 14368 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 927 ms 2021-03-06 12:23:37.718 INFO 14368 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2021-03-06 12:23:37.847 INFO 14368 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021-03-06 12:23:37.855 INFO 14368 --- [ main] com.example.DemoApplication : Started DemoApplication in 1.553 seconds (JVM running for 2.454)
访问:http://localhost:8080/test1
结果:(BeanPostProcessor实现类注入的普通bean和BeanPostProcessor实现类都未被AOP)说明:使用Controller中的第二种方法也是同样的结果。
testAOP方法(MyProcessor) testAOP方法(MyBean)
2.去掉BeanPostProcessor即可AOP
将 BeanPostProcessor实现类的implements BeanPostProcessor去掉,并去掉其覆写的方法。(其他都不变,比如:仍然实现Ordered接口)
结果为:(BeanPostProcessor实现类注入的普通bean和BeanPostProcessor实现类都被AOP)
before MyProcessor#testAOP testAOP方法(MyProcessor) before MyBean#testAOP testAOP方法(MyBean)
追踪
本处追踪上边“1.复现无法AOP的现象”的代码,入口:MyBean被实例化的源码
bean的实例化入口都是:AbstractBeanFactory#doGetBean,在这个地方打个条件断点:
启动项目,发现这个断点到达了三次,也就是说,有三个地方想实例化MyBean。
第一次:BeanPostProcessor实现类引用了MyBean
第二次:HelloController引用了MyBean
第三次:MyBean本身加入容器,当然要去实例化它
解决
法1:使用延迟初始化
代码
BeanPostProcessor的实现类中这样引入MyBean:
@Lazy @Autowired private MyBean myBean;
测试
启动时:
postProcessBeforeInitialization==> This is MyBean postProcessAfterInitialization==> This is MyBean
访问:http://localhost:8080/test1
结果(BeanPostProcessor实现类注入的普通bean可以AOP,BeanPostProcessor实现类未被AOP)
testAOP方法(MyProcessor) before MyBean#testAOP testAOP方法(MyBean)
法2:使用ApplicationContext
当然,此法在本处不好应用。可以应用在配置Shiro时注入Service。
private UserService getUserService() { return (UserService) applicationContext.getBean(UserService.class); }
坑3:注册方式及其限制
其他网址
简介
如何将BeanPostProcessor注册到Spring容器中?方式主要有如下两种:
- 将其声明在Spring的配置类或xml文件中,作为普通的bean,让ApplicationContext对象去加载它,这样它就被自动注册到容器中了。而且Spring容器会对BeanPostProcessor的实现类做特殊处理,即会将它们挑选出来,在加载其他bean前,优先加载BeanPostProcessor的实现类。
- 使用ConfigurableBeanFactory接口的addBeanPostProcessor方法手动添加。ApplicationContext对象中组合了一个ConfigurableBeanFactory的实现类对象。但这样添加BeanPostProcessor有一些缺点,如下:
- 一创建Spring容器,配置文件中的单例bean就会被加载,此时addBeanPostProcessor方法还没执行,那手动添加的BeanPostProcessor就无法作用于这些bean,所以手动添加的BeanPostProcessor只能作用于延迟加载的bean,或者非单例bean。
- Ordered接口的作用将失效,而是以注册的顺序执行。前面提过,Ordered接口用来指定多个BeanPostProcessor实现的方法的执行顺序。这是Spring官方文档中提到的:While the recommended approach for BeanPostProcessor registration is through ApplicationContext auto-detection (as described above), it is also possible to register them programmatically against a ConfigurableBeanFactory using the addBeanPostProcessor method. This can be useful when needing to evaluate conditional logic before registration, or even for copying bean post processors across contexts in a hierarchy. Note however that BeanPostProcessor s added programmatically do not respect the Ordered interface. Here it is the order of registration that dictates the order of execution. Note also that BeanPostProcessor s registered programmatically are always processed before those registered through auto-detection, regardless of any explicit ordering.
坑4:使用@Bean配置的限制
其他网址
简介
如果使用Java类的方式配置Spring,并使用@Bean声明一个工厂方法返回bean实例,那么返回值的类型必须是BeanPostProcessor类型,或者等级低于BeanPostProcessor的类型。
演示
package com.example.processor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; @Component public class MyProcessor implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization==> " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization==> " + beanName); return bean; } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }
在配置类中,声明MyProcessor可以有以下几种方式
@Configuration public class BeanConfig { // 方式1:MyProcessor @Bean public MyProcessor myProcessor() { return new MyProcessor(); } // 方式2:返回值为BeanPostProcessor @Bean public BeanPostProcessor myProcessor() { return new MyProcessor(); } // 方式3:返回值为Ordered @Bean public Ordered postBean() { return new MyProcessor(); } }
以上三种方式都可以让Spring容器创建MyProcessor实例对象,因为MyProcessor实现了BeanPostProcessor和Ordered接口,所以它的对象也是这两种类型的对象。但是需要注意,上面三种方式中,只有第一种和第二种方式,会让Spring容器将MyProcessor当作BeanPostProcessor处理;而第三种方式,则会被当作一个普通Bean处理,实现BeanPostProcessor的两个方法都不会被调用。因为在MyProcessor的继承体系中,Ordered和BeanPostProcessor是同级别的,Spring无法识别出这个Ordered对象,也是一个BeanPostProcessor对象;但是使用MyProcessor却可以,因为MyProcessor类型就是BeanPostProcessor的子类型。所以,在使用@Bean声明工厂方法返回BeanPostProcessor实现类对象时,返回值必须是BeanPostProcessor类型,或者更低级的类型。Spring官方文档中,这一部分的内容如下:
Note that when declaring a BeanPostProcessor using an @Bean factory method on a configuration class, the return type of the factory method should be the implementation class itself or at least the org.springframework.beans.factory.config.BeanPostProcessor interface, clearly indicating the post-processor nature of that bean. Otherwise, the ApplicationContext won’t be able to autodetect it by type before fully creating it. Since a BeanPostProcessor needs to be instantiated early in order to apply to the initialization of other beans in the context, this early type detection is critical.