• spring高级篇(二)


    1、Aware和InitializingBean

            Aware和InitializingBean都与Bean的生命周期管理相关。

            Aware接口:

    • 概念: Aware接口是Spring框架中的一个标记接口,它表示一个类能够感知到(aware of)Spring容器的存在及其特定的环境。Spring框架提供了多个Aware接口,如ApplicationContextAware、BeanFactoryAware、BeanNameAware等。
    • 区别:Aware接口通过回调方法的方式,让实现了该接口的类能够获取到Spring容器的一些资源或信息,比如ApplicationContextAware可以获取到Spring应用上下文,BeanFactoryAware可以获取到Bean工厂,BeanNameAware可以获取到Bean的名称等。
    • 联系:Aware接口可以与InitializingBean接口一起使用,通过Aware接口获取到Spring容器的资源后,可以在初始化Bean时使用这些资源。

            InitializingBean接口:

    • 概念: InitializingBean接口是Spring框架中的一个接口,实现该接口的类可以在Bean初始化完成后执行特定的逻辑。
    • 区别:InitializingBean接口中定义了一个初始化方法afterPropertiesSet(),当Bean的所有属性被设置后,Spring容器会调用该方法。在这个方法中,可以执行一些初始化操作,比如数据加载、资源初始化等。
    • 联系:InitializingBean接口可以与Aware接口一起使用,通过Aware接口获取到Spring容器的资源后,在InitializingBean的afterPropertiesSet()方法中进行必要的初始化操作。
    1.1、案例一

            下面的MyBean类分别实现了BeanNameAware, ApplicationContextAware,InitializingBean三个接口:

    1. public class MyBean implements BeanNameAware, ApplicationContextAware,InitializingBean {
    2. private static final Logger log = LoggerFactory.getLogger(MyBean.class);
    3. /**
    4. * 重写BeanNameAware的方法,可以获取当前bean的名称
    5. */
    6. @Override
    7. public void setBeanName(String name) {
    8. log.info("bean is {},bean name is:{}", this,name);
    9. }
    10. /**
    11. * 重写ApplicationContext的方法,允许Bean实现类获取对Spring应用上下文(ApplicationContext)的引用。
    12. */
    13. @Override
    14. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    15. log.info("bean is {},bean applicationContext is:{}", this,applicationContext);
    16. }
    17. /**
    18. * 重写InitializingBean的方法用于在Bean初始化完成后执行特定的逻辑。
    19. */
    20. @Override
    21. public void afterPropertiesSet() throws Exception {
    22. log.info("bean is {},init", this);
    23. }
    24. }
    1. public class A05 {
    2. public static void main(String[] args) {
    3. GenericApplicationContext context = new GenericApplicationContext();
    4. context.registerBean("mybean", MyBean.class);
    5. context.refresh();
    6. context.close();
    7. }
    8. }

            上面的功能,通过@Autowired @PostConstruct 等注解也可以实现,为什么还要实现接口?原因是因为,解析@Autowired @PostConstruct 等注解需要通过后处理器,而Aware等接口属于Spring的内置功能,不需要任何扩展(在上述案例的主类中,使用的是GenericApplicationContext,不会加任何后处理器)。扩展在某些时候可能会失效,而内置功能不会失效。

            演示使用@Autowired @PostConstruct 等注解实现同等功能:

    1. public class MyBean2 {
    2. private static final Logger log = LoggerFactory.getLogger(MyBean2.class);
    3. @Autowired
    4. public void getApplicationContext(ApplicationContext applicationContext) {
    5. log.info("bean is {},bean applicationContext is:{}", this,applicationContext);
    6. }
    7. @PostConstruct
    8. public void init(){
    9. log.info("init bean");
    10. }
    11. }

            在主类中需要加入后处理器:

    1. public class A05 {
    2. public static void main(String[] args) {
    3. GenericApplicationContext context = new GenericApplicationContext();
    4. context.registerBean("mybean2", MyBean2.class);
    5. context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
    6. context.registerBean(CommonAnnotationBeanPostProcessor.class);
    7. context.refresh();
    8. context.close();
    9. }
    10. }

            运行结果与实现ApplicationContextAware,InitializingBean相同。

    1.2、注解失效分析

            演示一种注解失效的情况:

    1. /**
    2. * 演示后处理器扩展失效的情况
    3. */
    4. @Configuration
    5. public class MyConfig1 {
    6. private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);
    7. @Autowired
    8. public void setApplicationContext(ApplicationContext applicationContext){
    9. log.info("注入 Application Context");
    10. }
    11. @PostConstruct
    12. public void init(){
    13. log.info("init");
    14. }
    15. /**
    16. * 想要获取MyConfig1中的BeanFactoryPostProcessor,就先要把MyConfig1类先创建好
    17. * 提前创建导致扩展功能失效 因为1、beanFactory后处理器 2、bean后处理器 这样的顺序
    18. * @return
    19. */
    20. @Bean
    21. public BeanFactoryPostProcessor beanPostProcessor(){
    22. return beanFactory -> {
    23. log.info("beanPostProcessor");
    24. };
    25. }
    26. }
    1. public class A05Application {
    2. public static void main(String[] args) {
    3. /*
    4. @AutoWired等注解是spring的扩展功能,需要通过后处理器实现
    5. 而实现BeanNameAware, ApplicationContextAware,InitializingBean 等接口是spring的内置功能
    6. 为了防止扩展功能失效
    7. */
    8. GenericApplicationContext context = new GenericApplicationContext();
    9. context.registerBean("myConfig", MyConfig1.class);
    10. context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
    11. context.registerBean(CommonAnnotationBeanPostProcessor.class);
    12. context.registerBean(ConfigurationClassPostProcessor.class);
    13. //执行顺序:1、beanFactory后处理器 2、bean后处理器 3、初始化所有单例
    14. context.refresh();
    15. context.close();
    16. }
    17. }

            此时发现,只有beanPostProcessor()成功执行

            原因在于,调用GenericApplicationContext的.refresh(); 方法初始化时,其执行顺序是:

            1、beanFactory后处理器

            2、bean后处理器

            3、初始化所有单例

            如下图所示:

            如果要初始化BeanFactoryPostProcessor,首先要把 MyConfig1类创建完成。相当于下图的顺序:

            很明显此时是先执行了类的初始化,最后再注册后处理器。这样就导致了无法解析@Autowired @PostConstruct 等注解。但是在创建类及初始化时,会执行Aware和InitializingBean相关接口:

    1. /**
    2. * 演示后处理器扩展失效的情况解决方案
    3. * 使用实现接口的方式
    4. */
    5. @Configuration
    6. public class MyConfig2 implements ApplicationContextAware, InitializingBean {
    7. private static final Logger log = LoggerFactory.getLogger(MyConfig2.class);
    8. /**
    9. * 想要获取MyConfig1中的BeanFactoryPostProcessor,就先要把MyConfig1类先创建好
    10. * 提前创建导致扩展功能失效 因为1、beanFactory后处理器 2、bean后处理器 这样的顺序
    11. * @return
    12. */
    13. @Bean
    14. public BeanFactoryPostProcessor beanPostProcessor(){
    15. return beanFactory -> {
    16. log.info("beanPostProcessor");
    17. };
    18. }
    19. @Override
    20. public void afterPropertiesSet() throws Exception {
    21. log.info("init");
    22. }
    23. @Override
    24. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    25. log.info("注入 Application Context");
    26. }
    27. }

               此时可以观察到,初始化和注入Application Context的操作都成功执行。

    2、初始化和销毁

    2.1、初始化和销毁的方式

            Bean初始化和销毁通常有三种方式:

    • 通过特定的生命周期回调方法来实现。主要涉及到以下两个接口:InitializingBean: 这是 Spring 框架提供的一个接口,其中定义了afterPropertiesSet()方法,该方法会在 Bean 的所有属性都被设置之后进行回调,用于执行自定义的初始化逻辑。DisposableBean: 这也是 Spring 框架提供的接口,其中定义了一个destroy()方法,该方法会在 Spring 容器销毁该 Bean 之前进行回调,用于执行自定义的销毁逻辑。
    • 使用 @PostConstruct @PreDestroy 注解
    • 通过设置@Bean注解的initMethod和destroyMethod

            初始化的三种方式:

    1. /**
    2. * 演示三种初始化的执行顺序
    3. */
    4. public class Bean1 implements InitializingBean {
    5. private static final Logger log = LoggerFactory.getLogger(Bean1.class);
    6. @PostConstruct
    7. public void init1(){
    8. log.info(" @PostConstruct方式");
    9. }
    10. /**
    11. * Invoked by the containing {@code BeanFactory} after it has set all bean properties
    12. * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
    13. *

      This method allows the bean instance to perform validation of its overall

    14. * configuration and final initialization when all bean properties have been set.
    15. *
    16. * @throws Exception in the event of misconfiguration (such as failure to set an
    17. * essential property) or if initialization fails for any other reason
    18. */
    19. @Override
    20. public void afterPropertiesSet() throws Exception {
    21. log.info("实现InitializingBean接口方式");
    22. }
    23. public void init3(){
    24. log.info("@Bean设置initMethod方式");
    25. }
    26. }

            销毁的三种方式:

    1. /**
    2. * 演示三种销毁方法的执行顺序
    3. */
    4. public class Bean2 implements DisposableBean {
    5. private static final Logger log = LoggerFactory.getLogger(Bean2.class);
    6. @PreDestroy
    7. public void destory1(){
    8. log.info("@PreDestroy方式");
    9. }
    10. /**
    11. * Invoked by the containing {@code BeanFactory} on destruction of a bean.
    12. *
    13. * @throws Exception in case of shutdown errors. Exceptions will get logged
    14. * but not rethrown to allow other beans to release their resources as well.
    15. */
    16. @Override
    17. public void destroy() throws Exception {
    18. log.info("实现DisposableBean接口方式");
    19. }
    20. public void destory3(){
    21. log.info("@Bean设置destroyMethod方式");
    22. }
    23. }
     2.2、初始化和销毁的顺序
    1. @SpringBootApplication
    2. public class A06Application {
    3. public static void main(String[] args) {
    4. ConfigurableApplicationContext context = SpringApplication.run(A06Application.class, args);
    5. context.close();
    6. }
    7. @Bean(initMethod = "init3")
    8. public Bean1 bean1(){
    9. return new Bean1();
    10. }
    11. @Bean(destroyMethod = "destory3")
    12. public Bean2 bean2(){
    13. return new Bean2();
    14. }
    15. }

             三种方式的先后顺序:

    3、Bean的Scope

            在 Spring 中,Scope(作用域)是指 Bean 的生命周期范围,即在应用程序中创建的 Bean 对象在何时被创建、存在多久以及何时被销毁。Spring 框架提供了多种作用域,每种作用域都决定了 Bean 实例的生命周期和可见性。

            列举一下常见的作用域:

    • Singleton(单例):在 Singleton 作用域下,Spring 容器中的每个 Bean 都只有一个实例,这个实例在整个应用程序的生命周期中都是共享的。默认情况下,所有在 Spring 容器中声明的 Bean 都是 Singleton 作用域,Spring的Bean默认是单例的。
    • Prototype(多例):在 Prototype 作用域下,每次向 Spring 容器请求该 Bean 时,都会创建一个新的实例。每次请求都会返回一个新的 Bean 实例,这些实例彼此之间互不影响。
    • Request(请求):在 Web 应用程序中,每个 HTTP 请求都会创建一个新的 Bean 实例,该实例仅在当前 HTTP 请求内有效。这意味着每个请求都会拥有一个独立的 Bean 实例,适用于 Web 应用程序中需要与请求相关联的 Bean。
    • Session(会话):在 Web 应用程序中,每个 HTTP 会话(Session)都会创建一个新的 Bean 实例,该实例在整个会话期间都是有效的。这意味着在同一个会话中,多个请求共享相同的 Bean 实例。此外还有一个全局会话,但仅在使用基于portlet的Web环境时才有意义。
    • Application(应用):该作用域是在一个 ServletContext 范围内有效,它将 Spring Bean 的生命周期绑定到 ServletContext 的生命周期,一个应用中的所有请求都共享同一个 Bean 实例。

            下面通过案例演示Application、Request、Session三种作用域:

    1. @Scope("application")
    2. @Component
    3. public class BeanForApplication {
    4. private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);
    5. @PreDestroy
    6. public void destory(){
    7. log.info("destory");
    8. }
    9. }
    1. @Scope("request")
    2. @Component
    3. public class BeanForRequest {
    4. private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);
    5. @PreDestroy
    6. public void destory(){
    7. log.info("Destory");
    8. }
    9. }
    1. @Component
    2. @Scope("session")
    3. public class BeanForSession {
    4. private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);
    5. @PreDestroy
    6. public void destroy() {
    7. log.info("destory");
    8. }
    9. }
    1. @Controller
    2. public class TestController {
    3. @Autowired
    4. @Lazy
    5. private BeanForApplication beanForApplication;
    6. @Autowired
    7. @Lazy
    8. private BeanForRequest beanForRequest;
    9. @Autowired
    10. @Lazy
    11. private BeanForSession beanForSession;
    12. /**
    13. * 在同一个浏览器内,每次请求beanForRequest都会不一样
    14. * 在不同的浏览器中,每次请求beanForRequest都不一样,第一次请求时beanForSession不一样
    15. * 同一个程序下,beanForApplication都是一样的
    16. *
    17. * 每次请求beanForRequest创建 销毁
    18. * beanForSession 到期时销毁
    19. * @param req
    20. * @param httpSession
    21. */
    22. @GetMapping("/test")
    23. public void test(HttpServletRequest req, HttpSession httpSession) {
    24. System.out.println("BeanForApplication:"+beanForApplication);
    25. System.out.println("BeanForRequest:"+beanForRequest);
    26. System.out.println("BeanForSession:"+beanForSession);
    27. }
    28. }

            主启动类:

    1. @SpringBootApplication
    2. public class A07Application {
    3. public static void main(String[] args) {
    4. ConfigurableApplicationContext context = SpringApplication.run(A07Application.class, args);
    5. }
    6. }

            使用同一浏览器,第一次执行:

            第二次执行:

    可以得出结论:同一浏览器(http会话)中,每次请求都会创建一个新的 Bean 实例,该实例仅在当前 HTTP 请求内有效。

            使用不同浏览器,浏览器一:

             浏览器二:

    可以得出结论:不同浏览器(http会话)都会产生一个新的实例,并且在每个会话中,每次请求都会创建一个新的 Bean 实例,该实例仅在当前 HTTP 请求内有效。

    3.1、Scope失效现象

            定义两个多例Bean:

    1. @Component
    2. @Scope("prototype")
    3. public class F1 {
    4. }
    1. @Component
    2. @Scope("prototype")
    3. public class F2 {
    4. }

            在配置类中通过@Autowired注入F1和F2

    1. @Component
    2. public class E {
    3. @Autowired
    4. private F1 f1;
    5. @Autowired
    6. private F2 f2;
    7. public F1 getF1() {
    8. return f1;
    9. }
    10. public F2 getF2() {
    11. return f2;
    12. }
    13. }

            在主类中获取三次F1和F2

    1. @ComponentScan("com.itbaima.a08_1")
    2. public class A08Test {
    3. private static final Logger log = LoggerFactory.getLogger(A08Test.class);
    4. public static void main(String[] args) {
    5. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08Application.class);
    6. E e = context.getBean(E.class);
    7. log.info("{}",e.getF1());
    8. log.info("{}",e.getF1());
    9. log.info("{}",e.getF1());
    10. System.out.println("<<<<<<<<<<<<<<<<<<<<<");
    11. log.info("{}",e.getF2());
    12. log.info("{}",e.getF2());
    13. log.info("{}",e.getF2());
    14. }
    15. }

            期望的结果是每次获取到的F1和F2都是不同的实例:

            与预期结果不符。为什么定义的Bean是多例,每次却获取到了同一个实例?

    当我们将一个 Prototype Bean 注入到一个 Singleton Bean 中时, Prototype Bean 注入的是该 Prototype Bean 的实例,而不是一个代理对象。这意味着,虽然 Prototype Bean 是多例的,但在 Singleton Bean 中的引用始终是同一个实例,导致 Prototype Bean 的多例特性失效。

    1. @Autowired
    2. private F1 f1;
    3. @Autowired
    4. private F2 f2;

            这就是单例注入多例Bean失效问题

    3.1.1、解决方法一

            使用@Lazy 注解。 @Lazy 注解可以延迟 Bean 的初始化,即在第一次被请求时才进行初始化,而不是在容器启动时就创建。

            Spring 容器在初始化 Singleton Bean 时不会立即初始化注入的 Prototype Bean,而是在首次使用 Prototype Bean 时才进行初始化,从而保证每次获取的都是新的实例。

    1. @Autowired
    2. @Lazy
    3. private F1 f1;

            F1每次都是不同的实例:

    3.1.2、解决方法二

            在声明多例时:

            @Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)

            告诉 Spring 在注入 Prototype Bean 时使用代理对象,而不是直接注入 Prototype Bean 的实例。这样,每次从 Singleton Bean 中获取 Prototype Bean 时,实际上会通过代理对象获取一个新的 Prototype Bean 实例,从而保证了 Prototype Bean 的多例特性。

    3.1.3、解决方法三

            在 Singleton Bean 中通过 ApplicationContext 手动获取 Prototype Bean。每次需要使用 Prototype Bean 时,都通过 ApplicationContext 获取新的实例。

    1. //通过applicationContext 的 getBean
    2. @Autowired
    3. private ApplicationContext applicationContext;
    4. public F4 getF4(){
    5. return applicationContext.getBean(F4.class);
    6. }
    3.1.4、解决方法四

           使用ObjectFactory。ObjectFactory是 Spring 提供的一种工厂模式,它的实现是延迟加载的,所以每次调用 getObject()方法都会返回一个新的 Prototype Bean 实例,其原理与@Lazy类似,都是实现延迟加载。

    1. //通过对象工厂的方式
    2. @Autowired
    3. private ObjectFactory<F3> f3;
    4. public F3 getF3(){
    5. return f3.getObject();
    6. }

    4、动态代理

            Spring 中的动态代理是指在运行时生成代理对象,而不是在编译时就确定代理对象的类型。Spring 中常用的动态代理有两种方式:基于 JDK 的动态代理和基于 CGLIB 的动态代理。

    • 基于 JDK 的动态代理:JDK 动态代理是通过 Java 的反射机制来实现的。当目标类实现了接口时,Spring 就会使用 JDK 动态代理来创建代理对象。在运行时,Spring 创建一个实现了目标接口的代理类,并且这个代理类会持有一个InvacationHandler对象,通过 InvacationHandler 的invoke()方法来实现对目标方法的增强。在invoke()方法中,可以在目标方法执行前后插入额外的逻辑,实现 AOP 的功能。(AOP是基于动态代理的)
    • 基于 CGLIB 的动态代理:当目标类没有实现接口时,Spring 会使用 CGLIB(Code Generation Library)来创建代理对象。它可以在运行时动态生成一个目标类的子类,并重写目标类中的方法。在子类中,可以在目标方法执行前后插入额外的逻辑,实现 AOP 的功能。

            两者之间的区别与联系:

            实现方式:

    • JDK 动态代理是基于 Java 的反射机制来实现的,它要求目标类必须实现至少一个接口。
    • CGLIB 动态代理则是通过生成目标类的子类来实现的,因此不要求目标类实现接口。

            即:JDK动态代理需要目标类和代理类实现同一个接口,CGLIB动态代理时会生成目标类的子类。

            性能:

    • 通常情况下,JDK 动态代理的性能比 CGLIB 动态代理略高,因为它不需要生成子类,直接利用接口来进行代理。
    • CGLIB 动态代理的性能略低,因为它需要生成目标类的子类,并且在运行时对目标类的方法进行重写。

            但是从是否反射的角度上,CGLIB因为无需反射,性能是高于JDK动态代理的

            适用范围

    • JDK 动态代理适用于那些实现了接口的类,因为 JDK 动态代理是基于接口的。
    • CGLIB 动态代理则适用于那些没有实现接口的类,它可以直接对类进行代理。

            两者的共同点:

            JDK 动态代理和 CGLIB 动态代理的目的都是为了实现对目标类的增强,例如实现 AOP(面向切面编程)。在 Spring 中,无论是使用 JDK 动态代理还是 CGLIB 动态代理,开发者都是通过配置来决定使用哪种代理方式。Spring 会根据目标类是否实现接口来自动选择使用 JDK 动态代理还是 CGLIB 动态代理。JDK 动态代理和 CGLIB 动态代理都是在运行时生成代理对象,动态地在目标方法的执行前后插入额外的逻辑,实现对目标方法的增强。

            补充:动态代理与静态代理的区别:

    静态代理:在编译时就已经确定代理类和被代理类的关系,代理类是通过手动编写代码或者工具生成的,代理类和被代理类在编译时就确定了。

    动态代理:在运行时动态生成代理对象,不需要预先知道代理类和被代理类的关系,可以动态地在运行时创建代理对象,使得代理类的生成更加灵活。

    4.1、JDK动态代理

            首先创建一个接口:

    1. public interface Inter {
    2. void foo();
    3. }

            实现类:

    1. public class InterImpl implements Inter {
    2. private static final Logger log = LoggerFactory.getLogger(InterImpl.class);
    3. @Override
    4. public void foo() {
    5. log.info("foo");
    6. }
    7. }

            测试类:

    1. public class JdkProxyDemo {
    2. private static final Logger log = LoggerFactory.getLogger(JdkProxyDemo.class);
    3. public static void main(String[] args) {
    4. InterImpl inter = new InterImpl();
    5. //模拟jdk动态代理
    6. ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();//加载运行期间动态生成的字节码
    7. Inter instance = (Inter) Proxy.newProxyInstance(classLoader, new Class[]{Inter.class}, (o, method, objects) -> {
    8. System.out.println("before");
    9. //通过反射调用 参数一:调用方法所在的对象 参数二:调用方法所需的参数
    10. Object invoke = method.invoke(inter, args);
    11. System.out.println("after");
    12. return invoke;
    13. });
    14. instance.foo();
    15. }
    16. }

           newProxyInstance( ClassLoader loader, Class[] interfaces,InvocationHandler h ) 是Proxy类下的一个静态方法:

    • 参数一:类加载器,用于加载动态生成的代理类。在 Java 中,类加载器负责将类的字节码加载到 JVM 中,并生成对应的 Class 对象。动态代理需要动态生成代理类,因此需要一个类加载器来加载这些类。
    • 参数二:代理对象需要实现的接口列表。动态代理只能代理接口,因此这个参数是一个接口数组,指定了代理对象需要实现的接口列表。代理对象将会实现这些接口,并且在调用接口方法时会委托给 InvacationHandler 处理。
    • 参数三:调用处理器,用于处理代理对象的方法调用。InvacationHandler 是一个接口,它包含一个 invoke()方法,用于在代理对象上调用方法时执行的逻辑。当代理对象的方法被调用时,invoke()方法会被调用,传入代理对象、被调用方法和方法参数,开发者可以在这个方法中实现自定义的逻辑,比如记录日志、执行额外的操作等。

           简而言之,可以用目标类获取一个类加载器,还需要传入被代理对象所实现的接口,并且在InvacationHandler内编写被代理类方法增强的逻辑,以及通过反射调用被代理类中的某个方法。

    4.1.1、JDK动态代理的模拟实现

            模拟JDK动态代理的实现,首先创建一个待增强的目标类及接口:

    1. public interface Foo {
    2. void foo();
    3. int bar();
    4. }
    1. public class Target implements Foo{
    2. private static final Logger log = LoggerFactory.getLogger(Target.class);
    3. @Override
    4. public void foo() {
    5. log.info("foo");
    6. }
    7. @Override
    8. public int bar() {
    9. log.info("bar");
    10. return 100;
    11. }
    12. }

            上面提到过,当目标类实现了接口时,Spring 就会使用 JDK 动态代理来创建代理对象。在运行时,Spring 创建一个实现了目标接口的代理类。 所以创建一个代理类实现Foo接口:

    1. /**
    2. * 实现Foo接口,重写foo()和bar()方法
    3. */
    4. public class $Proxy0 implements Foo {
    5. @Override
    6. public void foo() {
    7. //foo方法增强逻辑
    8. //调用目标方法
    9. }
    10. @Override
    11. public int bar() {
    12. //bar方法增强逻辑
    13. //调用目标方法
    14. return 0;
    15. }
    16. }

            在测试类中:

    1. /**
    2. * 模拟jdk动态代理的实现
    3. */
    4. public class A11Application {
    5. private static final Logger log = LoggerFactory.getLogger(A11Application.class);
    6. public static void main(String[] args) {
    7. //在创建$Proxy0对象时,通过有参构造指定 增强的具体逻辑
    8. $Proxy0 proxy0 = new $Proxy0();
    9. proxy0.bar();
    10. proxy0.foo();
    11. }
    12. }

            这样写已经可以简单的实现Target中foo()方法和bar()方法的增强,但是,此时的增强逻辑是在编译时就写死的,很显然不符合动态代理的概念。我们需要的是,在运行期间动态的对目标类中的方法进行增强。

            引入一个InvacationHandler接口:

    1. public interface InvacationHandler {
    2. /**
    3. * @param proxy 代理类对象
    4. * @param method 需要增强的方法
    5. * @param objects 需要增强的方法的参数
    6. * @return
    7. * @throws Throwable
    8. */
    9. Object invoke($Proxy0 proxy, Method method, Object[] objects) throws Throwable;
    10. }

            应该在测试类创建$Proxy0对象时,将InvacationHandler作为参数传入,在InvacationHandler中动态的编写增强的逻辑,在$Proxy0中加入构造:

    1. InvacationHandler invacationHandler;
    2. public $Proxy0(InvacationHandler invacationHandler) {
    3. this.invacationHandler = invacationHandler;
    4. }

              foo()方法和bar()方法中调用invacationHandler的invoke()方法,其中第二个参数,需要我们在$Proxy0类加载时,初始化带增强的方法:

    1. static Method foo;
    2. static Method bar;
    3. static {
    4. try {
    5. foo = Foo.class.getMethod("foo");
    6. bar = Foo.class.getMethod("bar");
    7. } catch (NoSuchMethodException e) {
    8. throw new NoSuchMethodError(e.getMessage());
    9. }
    10. }

             $Proxy0类改造完成:

    1. public class $Proxy0 implements Foo {
    2. InvacationHandler invacationHandler;
    3. public $Proxy0(InvacationHandler invacationHandler) {
    4. this.invacationHandler = invacationHandler;
    5. }
    6. static Method foo;
    7. static Method bar;
    8. static {
    9. try {
    10. foo = Foo.class.getMethod("foo");
    11. bar = Foo.class.getMethod("bar");
    12. } catch (NoSuchMethodException e) {
    13. throw new NoSuchMethodError(e.getMessage());
    14. }
    15. }
    16. @Override
    17. public void foo() {
    18. try {
    19. invacationHandler.invoke(this,foo,new Object[0]);
    20. } catch (RuntimeException | Error e) {
    21. throw e;
    22. }catch (Throwable throwable){
    23. throw new UndeclaredThrowableException(throwable);
    24. }
    25. }
    26. @Override
    27. public int bar() {
    28. try {
    29. Object result = invacationHandler.invoke(this, bar, new Object[]{100});
    30. return ((int) result);
    31. } catch (RuntimeException | Error e) {
    32. throw e;
    33. }catch (Throwable throwable){
    34. throw new UndeclaredThrowableException(throwable);
    35. }
    36. }
    37. }

            在创建 $Proxy0对象时,就需要通过创建invacationHandler匿名内部类的方式传递一段逻辑:

            最终测试类:

    1. public class A11Application {
    2. private static final Logger log = LoggerFactory.getLogger(A11Application.class);
    3. public static void main(String[] args) {
    4. //在创建$Proxy0对象时,通过有参构造指定 增强的具体逻辑
    5. $Proxy0 proxy0 = new $Proxy0(new InvacationHandler() {
    6. @Override
    7. public Object invoke($Proxy0 proxy, Method method, Object[] objects) throws Throwable {
    8. System.out.println("before");
    9. //通过反射调用Target中的方法,objects
    10. Object invoke = method.invoke(new Target(), objects);
    11. return invoke;
    12. }
    13. });
    14. proxy0.bar();
    15. proxy0.foo();
    16. }
    17. }
    4.1.2、JDK动态代理源码

            下面我们借助arthas工具查看JDK动态代理的源码实现:

            安装工具:cmd打开终端->curl -O https://alibaba.github.io/arthas/arthas-boot.jar

            运行工具:java -jar arthas-boot.jar

            注意:使用工具时保持程序处在运行状态,且需要获取要查看类的类名信息

            选择对应的类:


            得到生成代理类的源码:

    1. /*
    2. * Decompiled with CFR.
    3. *
    4. * Could not load the following classes:
    5. * com.itbaima.a10.jdkproxy.Inter
    6. */
    7. package com.sun.proxy;
    8. import com.itbaima.a10.jdkproxy.Inter;
    9. import java.lang.reflect.InvocationHandler;
    10. import java.lang.reflect.Method;
    11. import java.lang.reflect.Proxy;
    12. import java.lang.reflect.UndeclaredThrowableException;
    13. public final class $Proxy2
    14. extends Proxy
    15. implements Inter {
    16. private static Method m1;
    17. private static Method m2;
    18. private static Method m3;
    19. private static Method m0;
    20. public $Proxy2(InvocationHandler invocationHandler) {
    21. super(invocationHandler);
    22. }
    23. static {
    24. try {
    25. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
    26. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
    27. m3 = Class.forName("com.itbaima.a10.jdkproxy.Inter").getMethod("foo", new Class[0]);
    28. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    29. return;
    30. }
    31. catch (NoSuchMethodException noSuchMethodException) {
    32. throw new NoSuchMethodError(noSuchMethodException.getMessage());
    33. }
    34. catch (ClassNotFoundException classNotFoundException) {
    35. throw new NoClassDefFoundError(classNotFoundException.getMessage());
    36. }
    37. }
    38. public final boolean equals(Object object) {
    39. try {
    40. return (Boolean)this.h.invoke(this, m1, new Object[]{object});
    41. }
    42. catch (Error | RuntimeException throwable) {
    43. throw throwable;
    44. }
    45. catch (Throwable throwable) {
    46. throw new UndeclaredThrowableException(throwable);
    47. }
    48. }
    49. public final String toString() {
    50. try {
    51. return (String)this.h.invoke(this, m2, null);
    52. }
    53. catch (Error | RuntimeException throwable) {
    54. throw throwable;
    55. }
    56. catch (Throwable throwable) {
    57. throw new UndeclaredThrowableException(throwable);
    58. }
    59. }
    60. public final int hashCode() {
    61. try {
    62. return (Integer)this.h.invoke(this, m0, null);
    63. }
    64. catch (Error | RuntimeException throwable) {
    65. throw throwable;
    66. }
    67. catch (Throwable throwable) {
    68. throw new UndeclaredThrowableException(throwable);
    69. }
    70. }
    71. public final void foo() {
    72. try {
    73. this.h.invoke(this, m3, null);
    74. return;
    75. }
    76. catch (Error | RuntimeException throwable) {
    77. throw throwable;
    78. }
    79. catch (Throwable throwable) {
    80. throw new UndeclaredThrowableException(throwable);
    81. }
    82. }
    83. }

            与JDK动态代理的模拟实现类似。不同点在于,源码中加上了方法中共有的.equals()、.hashCode()、.toString()方法。

            4.1.3、JDK动态代理字节码生成(了解)

            JDK生成代理类,并没有经历源码阶段,编译阶段,而是直接生成了字节码(ASM)。ASM技术是在运行期间动态生成字节码。

            可以通过插件查看生成的字节码:

            准备一个继承了反射包下Proxy类的代理类和接口:

    1. public class $Proxy0 extends Proxy implements Foo {
    2. protected $Proxy0(InvocationHandler h) {
    3. super(h);
    4. }
    5. @Override
    6. public void foo(){
    7. try {
    8. h.invoke(this,foo,null);
    9. } catch (Throwable e) {
    10. throw new UndeclaredThrowableException(e);
    11. }
    12. }
    13. static Method foo;
    14. static {
    15. try {
    16. foo = Foo.class.getMethod("foo");
    17. } catch (NoSuchMethodException e) {
    18. throw new NoSuchMethodError(e.getMessage());
    19. }
    20. }
    21. }
    1. public interface Foo {
    2. void foo() throws Throwable;
    3. }

            然后将代理类和接口进行编译(Ctrl+Shift+F9)

            右键如图所示,生成文件。

            文件中一些字节码指令的简单理解:

            构建一个类:

    cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/itheima/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/itheima/Foo"});
    

            定义类中的方法和成员变量:

    1. {
    2. fv = cw.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);
    3. fv.visitEnd();
    4. }

            生成byte字节码数组:

    return cw.toByteArray();

            然后我们将$Proxy0Dump字节码写入$Proxy0.class文件:

    1. public class TestProxy {
    2. public static void main(String[] args) throws Exception {
    3. //将$Proxy0Dump字节码写入$Proxy0.class文件
    4. byte[] dump = $Proxy0Dump.dump();
    5. FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
    6. fileOutputStream.write(dump,0,dump.length);
    7. fileOutputStream.close();
    8. }
    9. }

            发现字节码文件和$Proxy0一样:

            整体流程:

    1. public class TestProxy {
    2. public static void main(String[] args) throws Throwable {
    3. //生成$Proxy0Dump字节码
    4. byte[] dump = $Proxy0Dump.dump();
    5. //加载$Proxy0Dump字节码
    6. ClassLoader classLoader = new ClassLoader(){
    7. @Override
    8. protected Class<?> findClass(String name) throws ClassNotFoundException {
    9. return super.defineClass(name,dump,0,dump.length);
    10. }
    11. };
    12. //得到类对象
    13. Class<?> loadClass = classLoader.loadClass("com.itheima.$Proxy0");
    14. //利用反射创建对象
    15. Constructor<?> constructor = loadClass.getConstructor(InvacationHandler.class);
    16. Foo foo = (Foo) constructor.newInstance(new InvacationHandler() {
    17. @Override
    18. public Object invoke($Proxy0 proxy, Method method, Object[] objects) throws Throwable {
    19. System.out.println("before invoke");
    20. System.out.println("调用方法");
    21. return null;
    22. }
    23. });
    24. foo.foo();
    25. }
    26. }
            4.1.4、JDK反射优化

            JDK动态代理中,当调用invoke() 方法到达一定次数时(17)次,会触发反射优化。(类似于JUC锁批量重偏向)

    1. public class TestMethodInvoke {
    2. public static void main(String[] args) throws Exception {
    3. Method foo = TestMethodInvoke.class.getMethod("foo", int.class);
    4. for (int i = 0; i < 17; i++) {
    5. foo.invoke(null,i);
    6. show(i,foo);
    7. System.in.read();
    8. }
    9. }
    10. // 方法反射调用时, 底层 MethodAccessor 的实现类
    11. private static void show(int i, Method foo) throws Exception {
    12. Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
    13. getMethodAccessor.setAccessible(true);
    14. Object invoke = getMethodAccessor.invoke(foo);
    15. if (invoke == null) {
    16. System.out.println(i + ":" + null);
    17. return;
    18. }
    19. Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
    20. delegate.setAccessible(true);
    21. System.out.println(i + ":" + delegate.get(invoke));
    22. }
    23. public static void foo(int i){
    24. System.out.println("第"+i+"次调用");
    25. }
    26. }

             前16次反射调用的是JDK本地的api,效率较低。

            第十七次优化成了正常调用方法,没有通过反射,所以性能较高:

    4.2、CGLIB 动态代理
    4.2.1、CGLIB 动态代理的模拟实现

            编写目标类:

    1. /**
    2. * 待增强的目标类
    3. */
    4. public class Target {
    5. public void save1(){
    6. System.out.println("save1 ...");
    7. }
    8. public void save2(int i){
    9. System.out.println("save2 ..."+ i);
    10. }
    11. public void save3(long j){
    12. System.out.println("save3 ..."+ j);
    13. }
    14. }

            编写代理类,CGLIB 动态代理则是通过生成目标类的子类来实现的,不需要目标类和代理类实现一个共同的接口

    1. /**
    2. * CGLIB 动态代理则是通过生成目标类的子类来实现的
    3. */
    4. public class MockCglibProxy extends Target{
    5. MethodInterceptor interceptor;
    6. public void setInterceptor(MethodInterceptor interceptor) {
    7. this.interceptor = interceptor;
    8. }
    9. static Method save1;
    10. static Method save2;
    11. static Method save3;
    12. static {
    13. try {
    14. save1 = Target.class.getMethod("save1");
    15. save2 = Target.class.getMethod("save2", int.class);
    16. save3 = Target.class.getMethod("save3", long.class);
    17. } catch (NoSuchMethodException e) {
    18. throw new NoSuchMethodError(e.getMessage());
    19. }
    20. }
    21. @Override
    22. public void save1() {
    23. try {
    24. interceptor.intercept(this,save1,new Object[0],null);
    25. } catch (Throwable e) {
    26. throw new RuntimeException(e);
    27. }
    28. }
    29. @Override
    30. public void save2(int i) {
    31. try {
    32. interceptor.intercept(this,save2,new Object[]{10},null);
    33. } catch (Throwable e) {
    34. throw new RuntimeException(e);
    35. }
    36. }
    37. @Override
    38. public void save3(long j) {
    39. try {
    40. interceptor.intercept(this,save3,new Object[]{20L},null);
    41. } catch (Throwable e) {
    42. throw new RuntimeException(e);
    43. }
    44. }
    45. }

            测试类:

    1. public class TestProxy {
    2. public static void main(String[] args) {
    3. Target target = new Target();
    4. MockCglibProxy mockCglibProxy = new MockCglibProxy();
    5. mockCglibProxy.setInterceptor(new MethodInterceptor() {
    6. @Override
    7. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    8. System.out.println("before");
    9. return method.invoke(target,objects);
    10. }
    11. });
    12. target.save1();
    13. target.save2(0);
    14. target.save3(0);
    15. }
    16. }

             而CGLIB和JDK动态代理最大的区别在于:

            方法代理参数,可以保证每次方法都是正常调用,而不是通过反射。

    4.2.2、CGLIB 动态代理的原理

            首先我们将interceptor.intercept() 方法中所需的MethodProxy方法参数进行补充创建:

            定义原有方法:

    1. public void saveSuper1(){
    2. super.save1();
    3. }
    4. public void saveSuper2(int i){
    5. super.save2(i);
    6. }
    7. public void saveSuper3(long j){
    8. super.save3(j);
    9. }

            初始化MethodProxy:

    1. //初始化methodProxy
    2. static MethodProxy saveSuper1;
    3. static MethodProxy saveSuper2;
    4. static MethodProxy saveSuper3;

            创建对象:

    1. static {
    2. try {
    3. save1 = Target.class.getMethod("save1");
    4. save2 = Target.class.getMethod("save2", int.class);
    5. save3 = Target.class.getMethod("save3", long.class);
    6. //创建methodProxy对象
    7. //参数一:目标类 参数二:代理类 参数三:方法参数 参数四:增强方法 参数五:原有方法
    8. saveSuper1 = MethodProxy.create(Target.class, MockCglibProxy.class,"()V","save1","saveSuper1");
    9. saveSuper2 = MethodProxy.create(Target.class, MockCglibProxy.class,"(I)V","save2","saveSuper2");
    10. saveSuper3 = MethodProxy.create(Target.class, MockCglibProxy.class,"(J)V","save3","saveSuper3");
    11. } catch (NoSuchMethodException e) {
    12. throw new NoSuchMethodError(e.getMessage());
    13. }
    14. }

            将创建的MethodProxy对象传入增强方法中:

    1. //以下是带增强的方法。。。。
    2. @Override
    3. public void save1() {
    4. try {
    5. interceptor.intercept(this,save1,new Object[0],saveSuper1);
    6. } catch (Throwable e) {
    7. throw new RuntimeException(e);
    8. }
    9. }
    10. @Override
    11. public void save2(int i) {
    12. try {
    13. interceptor.intercept(this,save2,new Object[]{10},saveSuper2);
    14. } catch (Throwable e) {
    15. throw new RuntimeException(e);
    16. }
    17. }
    18. @Override
    19. public void save3(long j) {
    20. try {
    21. interceptor.intercept(this,save3,new Object[]{20L},saveSuper3);
    22. } catch (Throwable e) {
    23. throw new RuntimeException(e);
    24. }
    25. }

            在主类中,使用

    • methodProxy.invoke(); 内部无反射,结合目标使用
    • methodProxy.invokeSuper() 内部无反射,结合代理使用

            在调用methodProxy.invoke();methodProxy.invokeSuper() 时,又会产生一个新的代理类,从而避免反射:

            生成的代理类的父类是FastClass类型:

    简单来说,FastClass的作用:为代理类中的每个方法生成一个索引,并将方法的调用逻辑封装在一个数组中。当代理类的方法被调用时,CGLIB 会根据方法的索引直接定位到对应的调用逻辑,从而避免了每次都要通过反射查找方法的开销。

            所以FastClass是CGLIB避免反射的关键。

            下面同样通过模拟FastClass实现类的方式进行分析,首先模拟结合目标使用的methodProxy.invoke(); 对应的FastClass实现类:

    • public int getIndex(Signature signature) 方法:目的是为了获取方法的编号,是在代理类中MethodProxy.create()创建时执行。
    • public Object invoke(int index, Object target, Object[] args)方法:目的是执行对应编号的方法,在主类调用method.invoke()时执行

            在public Object invoke(int index, Object target, Object[] args)方法中,没有使用反射的方式。

    1. /**
    2. * 模拟FastClass的子类实现,配合目标使用,对应 method.invoke
    3. */
    4. public class TargetFastClass {
    5. Signature s1 = new Signature("save1","()V");
    6. Signature s2 = new Signature("save2","(I)V");
    7. Signature s3 = new Signature("save3","(J)V");
    8. /**
    9. * 获取方法的编号
    10. * 执行时机: 代理类中 MethodProxy.create创建时
    11. * @param signature
    12. * @return
    13. */
    14. public int getIndex(Signature signature){
    15. if (s1.equals(signature)){
    16. return 0;
    17. }else if (s2.equals(signature)){
    18. return 1;
    19. }else if (s3.equals(signature)){
    20. return 2;
    21. }
    22. return -1;
    23. }
    24. /**
    25. * 执行编号对应的方法
    26. * 执行时机 调用method.invoke
    27. * @param index
    28. * @param target
    29. * @param args
    30. * @return
    31. */
    32. public Object invoke(int index, Object target, Object[] args){
    33. if (index == 0){
    34. //正常调用目标的方法,没有通过反射
    35. ((Target) target).save1();
    36. }else if (index == 1){
    37. ((Target) target).save2(((int) args[0]));
    38. }else if (index == 2){
    39. ((Target) target).save3(((long) args[0]));
    40. }
    41. return null;
    42. }
    43. }

            然后模拟结合目标使用的methodProxy.invokeSuper(); 对应的FastClass实现类:

    1. /**
    2. * 模拟FastClass的子类实现,配合代理类使用,对应 methodProxy.invokeSuper
    3. * 生成时机:代理对象中MethodProxy.create的执行
    4. */
    5. public class ProxyFastClass {
    6. Signature s1 = new Signature("saveSuper1","()V");
    7. Signature s2 = new Signature("saveSuper2","(I)V");
    8. Signature s3 = new Signature("saveSuper3","(J)V");
    9. /**
    10. * 获取代理类中方法的编号(有三个增强方法,三个原始方法)
    11. * 获取的都是原始方法的编号
    12. * 执行时机: 代理类中 MethodProxy.create创建时
    13. * @param signature
    14. * @return
    15. */
    16. public int getIndex(Signature signature){
    17. if (s1.equals(signature)){
    18. return 0;
    19. }else if (s2.equals(signature)){
    20. return 1;
    21. }else if (s3.equals(signature)){
    22. return 2;
    23. }
    24. return -1;
    25. }
    26. /**
    27. * 执行编号对应的方法
    28. * 执行时机 调用method.invoke
    29. * @param index
    30. * @param proxy
    31. * @param args
    32. * @return
    33. */
    34. public Object invoke(int index, Object proxy, Object[] args){
    35. if (index == 0){
    36. //正常调用目标的方法,没有通过反射
    37. ((MockCglibProxy) proxy).saveSuper1();
    38. }else if (index == 1){
    39. ((MockCglibProxy) proxy).saveSuper2(((int) args[0]));
    40. }else if (index == 2){
    41. ((MockCglibProxy) proxy).saveSuper3(((long) args[0]));
    42. }
    43. return null;
    44. }
    45. public static void main(String[] args) {
    46. ProxyFastClass fastClass = new ProxyFastClass();
    47. int index = fastClass.getIndex(new Signature("saveSuper1","()V"));
    48. System.out.println(index);
    49. fastClass.invoke(index,new MockCglibProxy(),new Object[0]);
    50. }
    51. }

            此时public int getIndex(Signature signature) 方法获取的是代理类中三个原始方法的序号。(代理类中一共有六个方法,三个原始方法,三个增强方法),原因是增强的逻辑在主类中已经通过.setInterceptor() 传入,不需要再次调用代理中的增强方法。并且如果再次调用代理中的增强方法,会发生interceptor.intercept() 循环调用的问题。

    4.2.3、与JDK动态代理对比

            CGLIB与JDK动态代理性能上的区别在于,JDK动态代理是在达到一定的次数后取消反射机制,正常调用方法。而CGLIB运用methodProxy.invoke()和methodProxy.invokeSuper() 进行了优化,保证方法无需反射。

            JDK调用一个方法就对应一个代理。而CGLIB是一个代理类对应两个FastClass(配合目标、配合代理),每个FastClass可以对应多个方法。

  • 相关阅读:
    mysql的监控大屏
    Java后端同第三方服务建立Socket通信①Python编写脚本模拟第三方服务(基础版)
    使用 gcov/lcov/gcovr 在 Android APK 下获取代码覆盖率
    分布式事务解决方案
    图神经网络 | Python实现基于时空图卷积GNN + LSTM 模型预测(多变量方法)
    VUE3 之 列表动画 - 这个系列的教程通俗易懂,适合新手
    2022年前端面试题加答案
    SpringMVC概述与简单使用
    API架构的选择,RESTful、GraphQL还是gRPC
    #边学边记 必修5 高项:对人管理 第1章 项目人力资源管理 之 项目团队建设
  • 原文地址:https://blog.csdn.net/2301_77599076/article/details/137975651