Bean生命周期--BeanPostProcessor的坑

坑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:注册方式及其限制 

其他网址

谈谈Spring中的BeanPostProcessor接口 - 特务依昂 - 博客园

简介

如何将BeanPostProcessor注册到Spring容器中?方式主要有如下两种:

  1. 将其声明在Spring的配置类或xml文件中,作为普通的bean,让ApplicationContext对象去加载它,这样它就被自动注册到容器中了。而且Spring容器会对BeanPostProcessor的实现类做特殊处理,即会将它们挑选出来,在加载其他bean前,优先加载BeanPostProcessor的实现类。
  2. 使用ConfigurableBeanFactory接口的addBeanPostProcessor方法手动添加。ApplicationContext对象中组合了一个ConfigurableBeanFactory的实现类对象。但这样添加BeanPostProcessor有一些缺点,如下:
    1. 一创建Spring容器,配置文件中的单例bean就会被加载,此时addBeanPostProcessor方法还没执行,那手动添加的BeanPostProcessor就无法作用于这些bean,所以手动添加的BeanPostProcessor只能作用于延迟加载的bean,或者非单例bean。
    2. 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配置的限制

其他网址

谈谈Spring中的BeanPostProcessor接口 - 特务依昂 - 博客园

简介

如果使用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.

猜你喜欢

转载自blog.csdn.net/feiying0canglang/article/details/114297964