spring原理-springmvc原理-springboot原理

Spring高级

、容器与bean

a01 、容器接口(BeanFactoryApplicationContext的区别与联系)

①、到底什么是 BeanFactory

BeanFactory接口:

首先我们先了解一下BeanFactory接口:

我们写一段springboot项目的启动类如下:

@SpringBootApplication
public class A01Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
    }
}
  • 上述代码中 SpringApplication.run(A01Application.class, args)方法返回一个容器对象,我们使用idea查看这个容器类的继承关系(快捷键:Ctrl + alt + u)如下:

202205201026199.png

  • 我们发现ConfigurableApplicationContext继承与ApplicationContext

  • ApplicationContext继承与BeanFactory

到底什么是BeanFactory

  • 它是ApplicationContext的父接口

  • 它才是 Spring 的核心容器,主要的 ApplicationContext 实现都 [组合]了它的功能

    web环境下,我们打印ConfigurableApplicationContext的class对象发现它其实是AnnotationConfigServletWebServerApplicationContext类型

    image-20220519212347338.png

我们接着查看AnnotationConfigServletWebServerApplicationContext类的继承图,
按图所示:AnnotationConfigServletWebServerApplicationContext又间接继承了GenericApplicationContext

image-20220519212347338.png

我们跟进GenericApplicationContext源码:发现beanFactory是该类的成员变量。

image-20220519212347338.png

②、BeanFactory的功能

BeanFactory接口中的方法:

image-20220519212347338.png

  • 表面上只有 getBean
  • 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供

我们可以查看springboot默认的ConfigurableApplicationContext类中的BeanFactory的实际类型

   public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 查看实际类型
        // class org.springframework.beans.factory.support.DefaultListableBeanFactory
        System.out.println(beanFactory.getClass());
    }

从打印结果可以了解到实际类型为DefaultListableBeanFactory,所以这里以BeanFactory的一个实现类DefaultListableBeanFactory作为出发点,进行分析。

image-20220519212347338.png

这里我们暂且不细看DefaultListableBeanFactory,先看DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry,先选中它,然后双击,可以跳转到对应的源码,可以看到有个私有的成员变量singletonObjects

image-20220519212347338.png

知道了这些关系,我们可以利用反射获取singletonObjects进行查看代码如下:

  • 首先我们人为往spring容器中添加两个bean
//bean1
package com.colin.a01;
import org.springframework.stereotype.Component;
@Component
public class Component1 {

}
//bean2
package com.colin.a01;
import org.springframework.stereotype.Component;
@Component
public class Component2 {

}
  • 利用反射查看我们定义的bean是否在singletonObjects
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
            singletonObjects.setAccessible(true);
            Map<String, Object> map = (Map<String, Object>)  singletonObjects.get(beanFactory);
            System.out.println(beanFactory.getClass());
            map.entrySet().stream().filter(entry -> entry.getKey().startsWith("component")).forEach(System.out::println);
        }

结果如下:说明单例池在BeanFactory

image-20220519212347338.png

③、ApplicationContextBeanFactory 多点啥?
  • 多实现了四个接口:

    • MessageSource: 国际化功能,支持多种语言
    • ResourcePatternResolver: 通配符匹配资源路径
    • EnvironmentCapable: 环境信息,系统环境变量,.properties、.application.yml等配置文件中的值
    • ApplicationEventPublisher: 发布事件对象

    蓝色框标识:

image-20220519212347338.png

⑴、演示MessageSource: 国际化功能
  • 我们在resource下建立三个语言翻译的properties

    其实还应该有一个默认语言环境的properties名字为messages.properties当所有语言环境都不匹配时它就生效

    图省事没有创建,真实开发一定要创建

    image-20220519212347338.png
    内容如下:

    image-20220519212347338.png

  • 我们编写java代码从测试: 其中GenericApplicationContext是干净的ApplicationContext 什么功能都没有

    我们编程注入一个bean ,bean的类型为MessageSource

    调用GenericApplicationContextrefresh方法加载或刷新配置

    使用Locale指定语言环境

    说明: 真实开发,语言环境由前台浏览器携带请求头传递,这里只是演示,真实开发是使用@Bean注入messageSource只是演示

 public static void main(String[] args) {
        //干净的ApplicationContext 什么功能都没有
        GenericApplicationContext context = new GenericApplicationContext();

        //编程注入一个bean bean的类型为MessageSource
        context.registerBean("messageSource", MessageSource.class, () -> {
            ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
            ms.setDefaultEncoding("utf-8");
            ms.setBasename("messages");
            return ms;
        });

        //加载容器
        context.refresh();

        //测试
        System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
        System.out.println(context.getMessage("hi", null, Locale.CHINESE));
        System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
    }
  • 结果如下: 我们发现由于语言环境的不同spring帮我们返回来不同的键值。实现了国际化的功能

image-20220519212347338.png

注意:

  • ApplicationContext 中 MessageSource bean 的名字固定为 messageSource
  • 使用 SpringBoot 时,国际化文件名固定为 messages
  • 空的 messages.properties 也必须存在
⑵、演示ResourcePatternResolver: 通配符匹配资源路径
  • 获取类路径下的messages开头的配置文件
   //测试ResourcePatternResolver
    Resource[] resources = context.getResources("classpath:messages*.properties");
    for (Resource resource : resources) {
        System.out.println(resource);
    }

运行结果:
image-20220520105150713

  • 获取spring相关jar包中的spring.factories配置文件
 	//测试获取spring相关jar包中的spring.factories配置文件
    Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
    for (Resource resource : resources) {
        System.out.println(resource);
    }

运行结果:
image-20220520135821235

⑶、演示EnvironmentCapable: 环境信息

获取系统环境变量中的java_home和项目的application.yml中的server.port属性:

	//测试获取系统环境变量中的`java_home`和项目的`application.yml`中的`server.port`属性
    System.out.println(context.getEnvironment().getProperty("java_home"));
    System.out.println(context.getEnvironment().getProperty("server.port"));

结果如下:
image-20220520140322738

⑷、演示ApplicationEventPublisher: 发布事件对象(重要)

我们模拟用户注册发布事件:

  • 定义一个用户注册事件类,继承自ApplicationEvent
import org.springframework.context.ApplicationEvent;

public class UserRegisteredEvent extends ApplicationEvent {
    public UserRegisteredEvent(Object source) {
        super(source);
    }
}

  • 定义一个监听器类,主要用来监听事件,处理事件。类头上需要加@Component注解,将该类交给spring管理,定义一个处理事件的方法,参数类型为用户注册事件类的对象,方法头上需要加上@EventListener注解
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class UserRegisteredListener {
    @EventListener
    public void userRegist(UserRegisteredEvent event) {
        System.out.println("UserRegisteredEvent...");
        log.debug("{}", event);
    }
}
  • 定义一个service 用来模拟用户注册业务,注册完毕发送用户注册事件
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class UserService {
    @Autowired
    private ApplicationEventPublisher context;

    public void register(String username, String password) {
        log.debug("新用户注册,账号:" + username + ",密码:" + password);
        context.publishEvent(new UserRegisteredEvent(this));
    }
}
  • 在启动类模拟调用
 	//模拟调用事件
    UserService userService = context.getBean(UserService.class);
    userService.register("colin","123456");

结果如下:
image-20220520141533060

④、a01总结:

学到了什么?

a. BeanFactory 与 ApplicationContext 并不仅仅是简单接口继承的关系, ApplicationContext 组合并扩展了 BeanFactory 的功能

b. 又新学一种代码之间解耦途径(发布事件)

a02 、容器的实现

Spring 的发展历史较为悠久,因此很多资料还在讲解它较旧的实现,这里出于怀旧的原因,把它们都列出来,供大家参考

  • DefaultListableBeanFactory,是 BeanFactory 最重要的实现,像控制反转依赖注入功能,都是它来实现
  • ClassPathXmlApplicationContext,从类路径查找 XML 配置文件,创建容器(旧)
  • FileSystemXmlApplicationContext,从磁盘路径查找 XML 配置文件,创建容器(旧)
  • XmlWebApplicationContext,传统 SSM 整合时,基于 XML 配置文件的容器(旧)
  • AnnotationConfigWebApplicationContext,传统 SSM 整合时,基于 java 配置类的容器(旧)
  • AnnotationConfigApplicationContext,Spring boot 中非 web 环境容器(新)
  • AnnotationConfigServletWebServerApplicationContext,Spring boot 中 servlet web 环境容器(新)
  • AnnotationConfigReactiveWebServerApplicationContext,Spring boot 中 reactive web 环境容器(新)

另外要注意的是:后面这些带有 ApplicationContext 的类都是 ApplicationContext 接口的实现,但它们是组合了 DefaultListableBeanFactory 的功能,并非继承而来

①、DefaultListableBeanFactory

接着第一讲中的内容,执行以下代码,可以了解到

ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());

ConfigurableApplicationContext类内部组合的BeanFactory实际类型为DefaultListableBeanFactory

spring底层创建实体类就是依赖于这个类,所以它是BeanFactory接口最重要的一个实现类。

  • 模拟一下spring使用DefaultListableBeanFactory类创建其他实体类对象的过程。

    • 准备工作首先我们在类中定义一个配置类,并增加几个bean的声明

      package com.colin.a02;
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.support.AbstractBeanDefinition;
      import org.springframework.beans.factory.support.BeanDefinitionBuilder;
      import org.springframework.beans.factory.support.DefaultListableBeanFactory;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      import javax.annotation.Resource;
      
      public class TestBeanFactory {
          public static void main(String[] args) {
             
          }
      
          @Configuration
          static class Config {
              @Bean
              public Bean1 bean1() {
                  return new Bean1();
              }
      
              @Bean
              public Bean2 bean2() {
                  return new Bean2();
              }
          }
      
          static class Bean1 {
              public Bean1() {
                  log.debug("构造 Bean1()");
              }
          }
      
          static class Bean2 {
              
          }
      }
      
    • 紧接着我们在main方法中利用DefaultListableBeanFactory和bean的定义信息BeanDefinition把配置类注册到spring容器。打印所有的bean的定义信息。

      public static void main(String[] args) {
              //创建BeanFactory重要的实现类DefaultListableBeanFactory
              DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
      
              /**
               * 准备bean 的定义信息(class 类信息、scope作用域、初始化方法,销毁方法)
               */
              AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                      .genericBeanDefinition(Config.class)
                      .setScope("singleton")
                      .getBeanDefinition();
      
              //将bean定义注册到beanfactory
              beanFactory.registerBeanDefinition("config",beanDefinition);
      
              //查看所有bean的定义信息
              for (String name : beanFactory.getBeanDefinitionNames()) {
                  System.out.println(name);
              }
          }
      

      结果如下:

      image-20220520144204292
      我们通过结果发现:容器中只有config类,但在配置类中的@Bean标注的Bean1、Bean2都不在容器中。

      这是因为beanFactory没有能力处理@Configuration@Bean注解,我们需要为beanFactory添加一些Bean工厂的后处理器,让后处理器处理这些事情。

    • 我们为beanFactory注入常用的处理器,并再次打印容器中bean的所有定义

              // 给 BeanFactory 添加一些常用的后处理器
              AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
      
              //查看所有bean的定义信息
              for (String name : beanFactory.getBeanDefinitionNames()) {
                  System.out.println(name);
              }
      

      结果如下:

      image-20220520145005229
      我们发现除了config,又多了很多bean的定义信息,这些多的bean就是AnnotationConfigUtils为我们添加的。但还是没有我们自己定义的bean信息,这是因为,我们只是将后处理器放入到了容器中,并没有调用它们。

    • 我们接着手动调用这些后处理器(Bean工厂的后处理器都实现了BeanFactoryPostProcessor接口)

          //调用 BeanFactory 的后处理器
          // BeanFactory 后处理器主要功能,补充了一些 bean 定义
          System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
          beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
              //查看类型
              System.out.println(beanFactoryPostProcessor.getClass());
              //手动调用后处理器
              beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
          });
          System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
          //再次查看所有bean的定义信息
          for (String name : beanFactory.getBeanDefinitionNames()) {
              System.out.println(name);
          }
      
      

      输出结果:

      image-20220521145848842

      可以看到:我们手动调用了以下后处理器。

      • org.springframework.context.annotation.ConfigurationClassPostProcessor
      • org.springframework.context.event.EventListenerMethodProcessor

      再次查看所有bean的定义信息我们看到了bean1、bean2

    • 现在我们继续研究,我们已经看到了bean1的定义,那下面我们试一下这些bean能不能正常使用

      System.out.println(beanFactory.getBean(Bean1.class));
      

      结果:

      image-20220520165903527

      这里有一个细节(打印了 构造 Bean1()):说明只有我们getBean的时候 BeanFactory才会真正的根据bean的定义信息创建这个bean,BeanFactory并不会主动创建bean

    • 可以看到可以使用,那我们改造一下代码,改变一下代码把依赖注入加上

      //改造bean1 
      static class Bean1 {
              private static final Logger log = LoggerFactory.getLogger(Bean1.class);
      
              public Bean1() {
                  log.debug("构造 Bean1()");
              }
      
              @Autowired
              private Bean2 bean2;
      
              public Bean2 getBean2() {
                  return bean2;
              }
          }
      

      调用bean1的getBean2方法

       System.out.println(beanFactory.getBean(Bean1.class).getBean2());
      

      结果:

      image-20220520170326560
      我们发现打印为null,这是因为BeanFactory没有处理@Autowired的能力,那是不是缺少相应的后处理器呢?

      这里注意的是BeanFactory 后处理器主要功能,补充了一些 bean 定义。 而依赖注入是创建bean的时期要处理的。并不是用来补充 bean的 定义。

      我们是要添加后处理器,但这个后处理器不是针对BeanFactory的,而是针对bean,我们把针对 bean 的生命周期的各个阶段提供扩展的后处理器称为Bean 后处理器

      • AnnotationConfigUtilsy已经为我们添加了Bean 后处理器的定义信息,我们要将Bean 后处理器设置给BeanFactory让其发挥作用

        Bean 后处理器都实现了BeanPostProcessor接口

        //        System.out.println(beanFactory.getBean(Bean1.class));
        //        System.out.println(beanFactory.getBean(Bean1.class).getBean2());
        
                //给beanFactory设置bean后处理器
                beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().forEach(
                        beanPostProcessor -> {
                            System.out.println(">>>>>" + beanPostProcessor);
                            beanFactory.addBeanPostProcessor(beanPostProcessor);
                        }
                );
                //再次查看bean1
                System.out.println(beanFactory.getBean(Bean1.class));
                System.out.println(beanFactory.getBean(Bean1.class).getBean2());
        

        结果:
        image-20220520171718320

        我们可以看到:

        ​ 给beanFactory设置bean后处理器:

        org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
        org.springframework.context.annotation.CommonAnnotationBeanPostProcessor

        ​ bean1也已经有了bean2:

        com.colin.a02.TestBeanFactory$Bean1@33d512c1
        com.colin.a02.TestBeanFactory$Bean2@515c6049

    • 探究Bean后处理器有排序的逻辑

      • 准备工作定义一个接口Inter,再定义两个Bean,名称分别为Bean3和Bean4,都继承Inter,接着在Config中通过@Bean注解将Bean3和Bean4都加进Bean工厂中,然后在Bean1中定义一个Inter对象,通过@Autowired注解将实现类注入进来。

        @Configuration
            static class Config {
                @Bean
                public Bean1 bean1() {
                    return new Bean1();
                }
        
                @Bean
                public Bean2 bean2() {
                    return new Bean2();
                }
        
                //将bean3加进Bean工厂
                @Bean
                public Bean3 bean3() {
                    return new Bean3();
                }
         		//将bean4加进Bean工厂
                @Bean
                public Bean4 bean4() {
                    return new Bean4();
                }
            }
        
            //定义接口
            interface Inter {
        
            }
        
            //bean3
            static class Bean3 implements Inter {
        
            }
            //bean4
            static class Bean4 implements Inter {
        
            }
        
            static class Bean1 {
                private static final Logger log = LoggerFactory.getLogger(Bean1.class);
        
                public Bean1() {
                    log.debug("构造 Bean1()");
                }
        
                @Autowired
                private Bean2 bean2;
        
                public Bean2 getBean2() {
                    return bean2;
                }
        
                //注入Inter
                @Autowired
                private Inter bean3;
        
                public Inter getInter() {
                    return bean3;
                }
            }
        
            static class Bean2 {
               
            }
        
      • 由于BeanFactory不会主动创建bean,所以我们调用BeanFactory类中重要的方法preInstantiateSingletons()

        preInstantiateSingletons()方法会准备好了所有的单例bean

        我们从容器中取出bean1 然后调用getInter() 看成员变量bean3注入的类型到底是什么类型?

        public static void main(String[] args) {
               //.....以前代码
        
               //调用BeanFactory类中重要的方法preInstantiateSingletons准备好了所有的单例
                beanFactory.preInstantiateSingletons(); // 准备好所有单例
                //取出bean1
                Bean1 bean1 = beanFactory.getBean(Bean1.class);
                //调用getInter()
                System.out.println(bean1.getInter());
            }
        

        结果如下:

        image-20220521150919362

        • 我们发现注入的是bean3
        • 我们分析一下原因:@Autowired是根据类型注入,但容器中是Inter类型的有两个,bean3bean4,所以@Autowired会根据变量的名称去配置bean的名称,所以注入的是bean3
      • 我们接着实验,我们除了使用@Autowired注解还可以使用@Resource注解实现依赖注入。所以我们去除@Autowired使用@Resource并指明bean的名字,查看结果。

        //改造bean1
        static class Bean1 {
                private static final Logger log = LoggerFactory.getLogger(Bean1.class);
        
                public Bean1() {
                    log.debug("构造 Bean1()");
                }
        
                @Autowired
                private Bean2 bean2;
        
                public Bean2 getBean2() {
                    return bean2;
                }
                @Resource(name = "bean4")
                private Inter bean3;
        
                public Inter getInter() {
                    return bean3;
                }
            }
        

        image-20220521151725620

        确实注入对象变成了bean4

      • 我们接着如果@Autowired@Resource同时存在(正常开发,不会怎么干,这只是实验),会注入什么对象呢?

        //改造bean1
        static class Bean1 {
                private static final Logger log = LoggerFactory.getLogger(Bean1.class);
        
                public Bean1() {
                    log.debug("构造 Bean1()");
                }
        
                @Autowired
                private Bean2 bean2;
        
                public Bean2 getBean2() {
                    return bean2;
                }
            
                @Autowired
                @Resource(name = "bean4")
                private Inter bean3;
        
                public Inter getInter() {
                    return bean3;
                }
            }
        

        image-20220521152011850

        结果又变成了bean3,这是为什么呢? 我们研究一下。

        • 首先我们知道,BeanFactory并部具备处理这些依赖注入相关注解的能力,处理@Autowired@Resource都是bean后处理器工作。而这些后处理器是我们调用AnnotationConfigUtils工具类内的方法添加的。

        • 以往学习我们知道AnnotationConfigUtilsBeanFactory添加的bean后处理器主要有两个org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor(处理@Autowired
          org.springframework.context.annotation.CommonAnnotationBeanPostProcessor(处理@Resource

        • 我们点进去AnnotationConfigUtilsregisterAnnotationConfigProcessors()方法看一下源码

          image-20220521153346544

          • 我们发现AutowiredAnnotationBeanPostProcessor确实比CommonAnnotationBeanPostProcessor顺序靠前
        • AnnotationConfigUtils不仅为BeanFcatory添加了一些常用后处理器,同时也为BeanFcatory添加了默认的后处理排序规则, 补充后处理器内有order属性就是排序的依据,数字越小,排名越往前,反之越靠后。

          image-20220521153719324

        • 我们通过代码试着改变一下后处理顺序,看一下效果

          //改变bean后处理顺序前
                  System.out.println("改变顺序前");
                  beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().forEach(
                          beanPostProcessor -> {
                              System.out.println(">>>>>" + beanPostProcessor);
                          }
                  );
                  //改变顺序后
                  System.out.println("改变顺序后");
                  beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
                          .sorted(beanFactory.getDependencyComparator())
                          .forEach(beanPostProcessor -> {
                              System.out.println(">>>>" + beanPostProcessor);
                              beanFactory.addBeanPostProcessor(beanPostProcessor);
                  });
                  //调用BeanFactory类中重要的方法preInstantiateSingletons准备好了所有的单例
                  beanFactory.preInstantiateSingletons(); // 准备好所有单例
                  //取出bean1
                  Bean1 bean1 = beanFactory.getBean(Bean1.class);
                  //调用getInter()
                  System.out.println(bean1.getInter());
          

          结果:如下图所示,改变顺序后CommonAnnotationBeanPostProcessor排在了AutowiredAnnotationBeanPostProcessor之前,依赖注入只会进行一次,所以在依赖注入时先是@Resource被解析完成了依赖注入,@Autowired后被解析,但依赖注入已经完成,所以不起效果,所以注入的对象是bean4

          image-20220521154247249

⑴、DefaultListableBeanFactory总结:

学到了什么:

beanFactory 不会做的事
a.不会主动调用 BeanFactory 后处理器

b.不会主动添加 Bean 后处理器
c.不会主动初始化单例

d.不会解析: beanFactory 还不会解析 ${ } 与 #{ }

e. bean 后处理器会有排序的逻辑

②、常见 ApplicationContext 实现
  • 四个重要的ApplicationContext接口的实现类

    • ClassPathXmlApplicationContext
    • FileSystemXmlApplicationContext
    • AnnotationConfigApplicationContext
    • AnnotationConfigServletWebServerApplication

    image-20220521160816437

⑴、测试ClassPathXmlApplicationContext
  • resources下新建a02.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
      <!-- 控制反转, 让 bean1 被 Spring 容器管理 -->
      <bean id="bean1" class="com.colin.a02.A02.Bean1"/>
    
      <!-- 控制反转, 让 bean2 被 Spring 容器管理 -->
      <bean id="bean2" class="com.colin.a02.A02.Bean2">
          <!-- 依赖注入, 建立与 bean1 的依赖关系 -->
          <property name="bean1" ref="bean1"/>
      </bean>
    
    
  • main方法测试

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
    import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    import org.springframework.web.servlet.DispatcherServlet;
    import org.springframework.web.servlet.mvc.Controller;
    
    /*
        常见 ApplicationContext 实现
     */
    public class A02 {
        private static final Logger log = LoggerFactory.getLogger(A02.class);
    
        public static void main(String[] args) {
            testClassPathXmlApplicationContext();
        }
    
        // ⬇️较为经典的容器, 基于 classpath 下 xml 格式的配置文件来创建
        private static void testClassPathXmlApplicationContext() {
            ClassPathXmlApplicationContext context =
                    new ClassPathXmlApplicationContext("a02.xml");
    
            for (String name : context.getBeanDefinitionNames()) {
                System.out.println(name);
            }
    
            System.out.println(context.getBean(Bean2.class).getBean1());
        }
    
    

    测试结果:

    image-20220521161558226

⑵、测试FileSystemXmlApplicationContext
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;

/*
    常见 ApplicationContext 实现
 */
public class A02 {
    private static final Logger log = LoggerFactory.getLogger(A02.class);

    public static void main(String[] args) {
        testFileSystemXmlApplicationContext();
    }
    
// ⬇️基于磁盘路径下 xml 格式的配置文件来创建
    private static void testFileSystemXmlApplicationContext() {
        FileSystemXmlApplicationContext context =
                new FileSystemXmlApplicationContext(
                        "src\\main\\resources\\a02.xml");
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println(context.getBean(Bean2.class).getBean1());
    }
}

测试结果:

image-20220521161902156

⑶、测试AnnotationConfigApplicationContext

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;

/*
    常见 ApplicationContext 实现
 */
public class A02 {
    private static final Logger log = LoggerFactory.getLogger(A02.class);

    public static void main(String[] args) {
        testAnnotationConfigApplicationContext();
    }


    // ⬇️较为经典的容器, 基于 java 配置类来创建
    private static void testAnnotationConfigApplicationContext() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println(context.getBean(Bean2.class).getBean1());
    }

    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2(Bean1 bean1) {
            Bean2 bean2 = new Bean2();
            bean2.setBean1(bean1);
            return bean2;
        }
    }

    static class Bean1 {
    }

    static class Bean2 {

        private Bean1 bean1;

        public void setBean1(Bean1 bean1) {
            this.bean1 = bean1;
        }

        public Bean1 getBean1() {
            return bean1;
        }
    }
}

测试结果:

image-20220521162232075

⑷、测试AnnotationConfigServletWebServerApplication

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;

/*
    常见 ApplicationContext 实现
 */
public class A02 {
    private static final Logger log = LoggerFactory.getLogger(A02.class);

    public static void main(String[] args) {
        testAnnotationConfigServletWebServerApplicationContext();
    }


    // ⬇️较为经典的容器, 基于 java 配置类来创建, 用于 web 环境
    private static void testAnnotationConfigServletWebServerApplicationContext() {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    static class WebConfig {
        
       /**
         * 创建web 环境:
         * 声明一个web容器工厂,
         * 声明一个DispatcherServlet,
         * 将DispatcherServlet注册到web容器
         * 
         * @return
         */
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }
        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }
        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }
        //最简单的控制器 bean的名字就是访问路径 路径是:/hello
        @Bean("/hello")
        public Controller controller1() {
            return (request, response) -> {
                response.getWriter().print("hello");
                return null;
            };
        }
    }
}

测试结果:

image-20220521162705622

访问浏览器http://localhost:8080/hello

image-20220521162759057

⑸、模拟ApplicationContext 实现都干了什么?
  • ClassPathXmlApplicationContext示例

     public static void main(String[] args) {
            DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
            System.out.println("读取之前...");
            for (String name : beanFactory.getBeanDefinitionNames()) {
                System.out.println(name);
            }
            System.out.println("读取之后...");
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
            reader.loadBeanDefinitions(new ClassPathResource("a02.xml"));
            for (String name : beanFactory.getBeanDefinitionNames()) {
                System.out.println(name);
            }
     }
    

    测试结果:

    image-20220521163203424

⑹、总结

学到了什么

a. 常见的 ApplicationContext 容器实现

b. 内嵌容器、DispatcherServlet 的创建方法、作用

a03Bean 的生命周期

  • 一个受 Spring 管理的 bean,生命周期主要阶段有
    • 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
    • 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
    • 初始化:回调各种 Aware 接口,调用对象的各种初始化方法
    • 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
      • prototype 对象也能够销毁,不过需要容器这边主动调用

一些资料会提到,生命周期中还有一类 bean 后处理器:BeanPostProcessor,会在 bean 的初始化的前后,提供一些扩展逻辑。但这种说法是不完整的因为bean 后处理器可能在bean的各个生命周期里发挥作用。

①、演示1 - bean 生命周期
创建
依赖注入
初始化
可用
销毁
  • springboot项目启动类

    @SpringBootApplication
    public class A03 {
        public static void main(String[] args) {
            ConfigurableApplicationContext context = SpringApplication.run(A03.class, args);
            context.close();
        }
    }
    
  • 定义一个LifeCycleBean,加上@Component注解,再编写一些方法,给这些方法加上Bean的生命周期过程中的注解

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    @Slf4j
    @Component
    public class LifeCycleBean {
    
        public LifeCycleBean() {
            log.info("构造方法");
        }
    
        @Autowired
        public void autowire(@Value("${java_home}") String home) {
            log.info("依赖注入:{}", home);
        }
    
        @PostConstruct
        public void init() {
            log.info("初始化方法");
        }
    
        @PreDestroy
        public void destory() {
            log.info("销毁方法");
        }
    }
    

    结果打印:

    image-20220521164848266

    执行顺序是:

    构造方法 》》 依赖注入 》》 初始化方法 》》 销毁方法

  • 下面我们编写自定义Bean的后处理器,对lifeCycleBean的生命周期过程进行扩展,编写自定义Bean的后处理器需要实现InstantiationAwareBeanPostProcessorDestructionAwareBeanPostProcessor接口,并加上@Component注解。

    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.PropertyValues;
    import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
    import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
    import org.springframework.stereotype.Component;
    
    /**
     * 自定义bean后处理器
     */
    @Slf4j
    @Component
    public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {
    
        /**
         * 在销毁之前调用
         * @param bean
         * @param beanName
         * @throws BeansException
         */
        @Override
        public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
            if (beanName.equals("lifeCycleBean")) {
                log.info("<<<<<< 销毁之前执行 (postProcessBeforeDestruction), 如 @PreDestroy");
            }
        }
    
        /**
         * 实例化之前(未调用构造方法)执行
         * 有返回值 会替代原本的 bean
         * @param beanClass
         * @param beanName
         * @return
         * @throws BeansException
         */
        @Override
        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
            if (beanName.equals("lifeCycleBean")) {
                log.info("<<<<<< 实例化之前(未调用构造方法)执行(postProcessBeforeInstantiation), 这里返回的对象会替换掉原本的 bean");
            }
            return null;
        }
    
        /**
         * 实例化之后(调用构造方法后)执行
         * 如果返回 false 会跳过依赖注入阶段
         * @param bean
         * @param beanName
         * @return
         * @throws BeansException
         */
        @Override
        public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
            if (beanName.equals("lifeCycleBean")) {
                log.info("<<<<<< 实例化之后(调用构造方法后)执行(postProcessAfterInstantiation), 这里如果返回 false 会跳过依赖注入阶段");
    //            return false;
            }
            return true;
        }
    
        /**
         * 依赖注入阶段执行
         * @param pvs
         * @param bean
         * @param beanName
         * @return
         * @throws BeansException
         */
        @Override
        public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
            if (beanName.equals("lifeCycleBean")) {
                log.info("<<<<<< 依赖注入阶段执行(postProcessProperties), 如 @Autowired、@Value、@Resource");
            }
            return pvs;
        }
    
        /**
         * 初始化之前执行
         * 有返回值 会替代原本的 bean
         * @param bean
         * @param beanName
         * @return
         * @throws BeansException
         */
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (beanName.equals("lifeCycleBean")) {
                log.info("<<<<<< 初始化之前执行(postProcessBeforeInitialization), 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties");
            }
            return bean;
        }
    
        /**
         * 初始化之后执行
         * 有返回值 会替代原本的 bean
         * @param bean
         * @param beanName
         * @return
         * @throws BeansException
         */
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (beanName.equals("lifeCycleBean")) {
                log.info("<<<<<< 初始化之后执行(postProcessAfterInitialization), 这里返回的对象会替换掉原本的 bean, 如代理增强");
            }
            return bean;
        }
    }
    
    

    结果打印:

    image-20220521174030496

    postProcessAfterInstantiation返回值改为false,让bean跳过依赖注入阶段结果:依赖注入语句无打印

    image-20220521174206973

②、演示2 - 模板方法设计模式
  • 提高代码的扩展性。
import java.util.ArrayList;
import java.util.List;

public class TestMethodTemplate {

    public static void main(String[] args) {
        MyBeanFactory beanFactory = new MyBeanFactory();
        beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Autowired"));
        beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Resource"));
        beanFactory.getBean();
    }

    // 模板方法  Template Method Pattern
    static class MyBeanFactory {
        public Object getBean() {
            Object bean = new Object();
            System.out.println("构造 " + bean);
            System.out.println("依赖注入 " + bean); // @Autowired, @Resource
            for (BeanPostProcessor processor : processors) {
                processor.inject(bean);
            }
            System.out.println("初始化 " + bean);
            return bean;
        }

        private List<BeanPostProcessor> processors = new ArrayList<>();

        public void addBeanPostProcessor(BeanPostProcessor processor) {
            processors.add(processor);
        }
    }

    static interface BeanPostProcessor {
        public void inject(Object bean); // 对依赖注入阶段的扩展
    }
}
④、演示3 - bean 后处理器排序
package com.colin.a03;

import cn.hutool.core.collection.ListUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.Order;

import java.util.ArrayList;
import java.util.List;

/*
 * @Author Colin
 * @Description //TODO 测试后处理器的排序
 * @Date 2022/6/4 10:33
 **/
@Slf4j
public class TestProcessorOrder {

    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
        List<BeanPostProcessor> list = new ArrayList<>(ListUtil.of(new P1(), new P2(), new P3(), new P4(), new P5()));
        list.sort(beanFactory.getDependencyComparator());

        list.forEach(processor -> {
            processor.postProcessBeforeInitialization(new Object(), "");
        });
        /*
            学到了什么
                1. 实现了 PriorityOrdered 接口的优先级最高
                2. 实现了 Ordered 接口与加了 @Order 注解的平级, 按数字升序
                3. 其它的排在最后
         */
    }

    @Order(1)
    static class P1 implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            log.info("postProcessBeforeInitialization @Order(1)");
            return bean;
        }
    }

    @Order(2)
    static class P2 implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            log.info("postProcessBeforeInitialization @Order(2)");
            return bean;
        }
    }

    static class P3 implements BeanPostProcessor, PriorityOrdered {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            log.info("postProcessBeforeInitialization PriorityOrdered");
            return bean;
        }

        @Override
        public int getOrder() {
            return 100;
        }
    }

    static class P4 implements BeanPostProcessor, Ordered {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            log.info("postProcessBeforeInitialization Ordered");
            return bean;
        }


        @Override
        public int getOrder() {
            return 0;
        }
    }

    static class P5 implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            log.info("postProcessBeforeInitialization");
            return bean;
        }
    }
}

运行结果:

image-20220604104626933

我们可以从运行结果看到:

  • 实现了 PriorityOrdered 接口的优先级最高

  • 实现了 Ordered 接口与加了 @Order 注解的平级, 按数字升序

  • 其它的排在最后

a04Bean 后处理器

①、演示1 - 后处理器作用
  • Bean 后处理器是为Bean服务的,准备工作: 准备4个bean并加上响应的解析注解。

    • bean1

      package com.colin.a04;
      
      import lombok.ToString;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      
      import javax.annotation.PostConstruct;
      import javax.annotation.PreDestroy;
      
      @Slf4j
      @ToString
      public class Bean1 {
          private Bean2 bean2;
      
          @Autowired
          public void setBean2(Bean2 bean2) {
              log.info("@Autowired 生效:{}", bean2);
              this.bean2 = bean2;
          }
      
          @Autowired
          private Bean3 bean3;
      
          private String home;
      
          @Autowired
          public void setHome(@Value("${JAVA_HOME}") String home) {
              log.info("@Value 生效: {}", home);
              this.home = home;
          }
      
          @PostConstruct
          public void init() {
              log.debug("@PostConstruct 生效");
          }
      
          @PreDestroy
          public void destroy() {
              log.debug("@PreDestroy 生效");
          }
      
      }
      
    • bean2

      package com.colin.a04;
      
      public class Bean2 {
      }
      
    • bean3

      package com.colin.a04;
      
      public class Bean3 {
      }
      
    • bean4

      package com.colin.a04;
      
      import lombok.Getter;
      import lombok.Setter;
      import lombok.ToString;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      
      @Slf4j
      @Getter
      @Setter
      @ToString
      @ConfigurationProperties(prefix = "java")
      public class Bean4 {
      
          private String home;
          private String version;
      }
      
    • 我们开始测试,准备一个干净的spring容器【GenericApplicationContext】用编程方式注册这四个bean,他们会被注册为beanfactoryBeanDefinition我们再手动调用容器的初始化方法refresh()让其根据BeanDefinition创建单例bean

    package com.colin.a04;
    
    import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
    import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
    import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
    import org.springframework.context.support.GenericApplicationContext;
    
    public class A04 {
    
        public static void main(String[] args) {
            //⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            //⬇️用原始方法注册三个bean
            context.registerBean("bean1", Bean1.class);
            context.registerBean("bean2", Bean2.class);
            context.registerBean("bean3", Bean3.class);
            context.registerBean("bean4", Bean4.class);
            // ⬇️初始化容器
            context.refresh(); // 执行beanFactory后处理器, 添加bean后处理器, 初始化所有单例
            //打印bean4
            System.out.println(context.getBean(Bean1.class));
            //打印bean4
            System.out.println(context.getBean(Bean4.class));
            // ⬇️销毁容器
            context.close();
        }
    }
    
    

    结果:

    image-20220604112648064

​ 结果中没有任何@Value@Autowired以及初始化方法销毁方法的打印,bean1的依赖注入没有生效,bean4的属性也是空的。

  • 解决@Autowired@Value没有生效,为beanfactory 添加处理@Autowired@Value注解的bean后处理器AutowiredAnnotationBeanPostProcessor

    //解析@Autowired @Value的bean后处理器
    AutowiredAnnotationBeanPostProcessor
    context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
    

    运行结果:

    image-20220604112300732

    image-20220604112328780

    • 为什么会这样呢?

      • 这是因为AutowiredAnnotationBeanPostProcessor虽然有依赖注入的能力,但是它无法获取@Value的值${JAVA_HOME},获取值需要另外的类进行处理ContextAnnotationAutowireCandidateResolver
      • 解析${JAVA_HOME}需要用到${} 的解析器在初始化容器时refresh()会自动添加。
      //用来获取@Value的值 为beanfactory 设置自动装配解析器
      //ContextAnnotationAutowireCandidateResolver
      context.getDefaultListableBeanFactory().setAutowireCandidat2eResolver(new ContextAnnotationAutowireCandidateResolver());
      

      再次运行:

      image-20220604112908578
      此时的@Autowired@Value就已经生效了。

  • 解决@ConfigurationProperties没有生效,添加bean后处理器ConfigurationPropertiesBindingPostProcessor

    //解析 @ConfigurationProperties注解
    //ConfigurationPropertiesBindingPostProcessor
    ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());
    

    再次运行:

    image-20220604113248048
    bean4也恢复了正常。

  • 解决@PostConstruct以及PreDestroy没有生效,添加bean后处理器CommonAnnotationBeanPostProcessor

    //解析@Resource @PostConstruct @PreDestroy的bean后处理器
    //CommonAnnotationBeanPostProcessor
    // @Resource @PostConstruct @PreDestroy
    context.registerBean(CommonAnnotationBeanPostProcessor.class); 
    

    结果:

    image-20220604115043767

总体代码:

package com.colin.a04;

import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.context.support.GenericApplicationContext;

public class A04 {

    public static void main(String[] args) {
        //⬇️GenericApplicationContext 是一个【干净】的容器
        GenericApplicationContext context = new GenericApplicationContext();

        //⬇️用原始方法注册三个bean
        context.registerBean("bean1", Bean1.class);
        context.registerBean("bean2", Bean2.class);
        context.registerBean("bean3", Bean3.class);
        context.registerBean("bean4", Bean4.class);

        //用来获取@Value的值 为beanfactory 设置自动装配解析器
        //ContextAnnotationAutowireCandidateResolver
        context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        //解析@Autowired @Value的bean后处理器
        //AutowiredAnnotationBeanPostProcessor
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);

        //解析@Resource @PostConstruct @PreDestroy的bean后处理器
        //CommonAnnotationBeanPostProcessor
        //@Resource @PostConstruct @PreDestroy
        context.registerBean(CommonAnnotationBeanPostProcessor.class); 

        //解析 @ConfigurationProperties注解
        //ConfigurationPropertiesBindingPostProcessor
        ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

        // ⬇️初始化容器
        context.refresh(); // 执行beanFactory后处理器, 添加bean后处理器, 初始化所有单例
        System.out.println(context.getBean(Bean1.class));
        System.out.println(context.getBean(Bean4.class));
        // ⬇️销毁容器
        context.close();

    }

}

⑴、总结
  • @Autowired 等注解的解析属于 bean 生命周期阶段(依赖注入, 初始化)的扩展功能
  • 这些扩展功能由 bean 后处理器来完成
②、演示2 - @Autowired bean 后处理器运行分析
  • 原始步骤拆解

    package com.colin.a04;
    
    import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
    import org.springframework.core.env.StandardEnvironment;
    
    /*
     * @Author Colin
     * @Description //TODO 模拟autowired解析过程
     * @Date 2022/6/4 11:54
     **/
    public class DiInAutowired {
          public static void main(String[] args) {
            //测试原始步骤
            base();
    
        }
        static void base() {
            //创建beanfacory
            DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
            //注册bean
            beanFactory.registerSingleton("bean2", new Bean2());//new Bean2() 它会认为这是成品对象。会跳过创建过程(依赖注入,初始化)
            beanFactory.registerSingleton("bean3", new Bean3());
            //设置@value注解解析器
            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
            //手动设置 ${}解析器
            beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
            //创建处理@Autowired的bean后处理器
            AutowiredAnnotationBeanPostProcessor beanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
            beanPostProcessor.setBeanFactory(beanFactory);
    
            Bean1 bean1 = new Bean1();
            System.out.println("手动调用前处理器前");
            System.out.println(bean1);
            //手动调用后处理器
            beanPostProcessor.postProcessProperties(null, bean1, "bean1");
            System.out.println("手动调用后处理器后");
            beanPostProcessor.postProcessProperties(null, bean1, "bean1");
            System.out.println(bean1);
    
        }
    }
    
    

    测试结果:
    image-20220604120515166

    我们发现手动调用后处理器的postProcessProperties方法实现了依赖注入和@Value的解析,我们重点关注一下postProcessProperties方法。我们看一下源码:

    image-20220604120848810

我们看到源码里,它先是根据我们传递的参数找到了一个类型为InjectionMetadata的结果,然后调用了该类的inject方法。

  • 我们通过反射调用findAutowiringMetadata(),得到InjectionMetadata并调用inject()方法实现依赖注入。

    /**
     * processor.postProcessProperties(null, bean1, "bean1")
     * 第一步调用findAutowiringMetadata获取bean的Autowire注解的元数据
     * 我们用反射直接调用私有方法findAutowiringMetadata
     */
    Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
    findAutowiringMetadata.setAccessible(true);
    
    /**
     * 获取 Bean1 上加了 @Value @Autowired 的成员变量,方法参数信息
     */
    InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);
    System.out.println(metadata);
    
    /**
     * 2. 调用 InjectionMetadata 来进行依赖注入, 注入时按类型查找值
     */
    metadata.inject(bean1, "bean1", null);
    System.out.println(bean1);
    

    结果:
    image-20220604203601784

  • 依赖注入是如何按类型查找值的? 模拟按类型查找值

    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    //new Bean2() 它认为这是成品对象 所以会跳过创建过程(依赖注入,初始化)
    beanFactory.registerSingleton("bean2", new Bean2());
    beanFactory.registerSingleton("bean3", new Bean3());
    beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); // @Value
    beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders); // ${} 的解析器
    
    // 1. 查找哪些属性、方法加了 @Autowired, 这称之为 InjectionMetadata
    AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
    processor.setBeanFactory(beanFactory);
    
    
    //模拟按类型查找值
    /**
     * 3、如何按类型查找值
     */
    Field bean3 = Bean1.class.getDeclaredField("bean3");
    //spring会把属性封装成DependencyDescriptor对象(依赖描述对象)
    DependencyDescriptor dd1 = new DependencyDescriptor(bean3,false);
    Object o = beanFactory.doResolveDependency(dd1, null, null,null);
    System.out.println(o);
    
    Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
    DependencyDescriptor dd2 =
            new DependencyDescriptor(new MethodParameter(setBean2, 0), true);
    Object o1 = beanFactory.doResolveDependency(dd2, null, null, null);
    System.out.println(o1);
    
    Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
    DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
    Object o2 = beanFactory.doResolveDependency(dd3, null, null, null);
    System.out.println(o2);
    

    结果:
    image-20220604203924748

⑴、总结
  1. AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata 用来获取某个 bean 上加了 @Value @Autowired 的成员变量,方法参数的信息,表示为 InjectionMetadata
  2. InjectionMetadata 可以完成依赖注入
  3. InjectionMetadata 内部根据成员变量,方法参数封装为 DependencyDescriptor 类型
  4. 有了 DependencyDescriptor,就可以利用 beanFactory.doResolveDependency 方法进行基于类型的查找

a05BeanFactory 后处理器

①、演示1 - BeanFactory 后处理器的作用
⑴、演示ConfigurationClassPostProcessor
  • 准备工作:代码结构如下,compent包下有三个bean,compent父级目录含有一个bean1、配置类,以及测试类A05

    image-20220605154500342

  • 代码如下:

    • Bean2:

      @Slf4j
      @Component
      public class Bean2 {
          public Bean2() {
              log.info("我被spring管理啦");
          }
      }
      
    • Bean3

      @Slf4j
      @Controller
      public class Bean3 {
          public Bean3() {
              log.info("我被 Spring 管理啦");
          }
      }
      
    • Bean4

      @Slf4j
      public class Bean4 {
          public Bean4() {
              log.info("我被 Spring 管理啦");
          }
      }
      
    • Bean1

      @Slf4j
      public class Bean1 {
          public Bean1() {
              log.info("我被spring管理啦");
          }
      }
      
    • Config:

      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class Config {
      
          @Bean
          public Bean1 bean1() {
              return new Bean1();
          }
      
      
      }
      
  • 准备工作完毕,我们开始编写测试类,准备一个干净的容器。并将配置类【Config类】注册到容器中,并进行容器的初始化。

    package com.colin.a05;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.ConfigurationClassPostProcessor;
    import org.springframework.context.support.GenericApplicationContext;
    
    @Slf4j
    public class A05 {
        public static void main(String[] args) {
    
    
            // ⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
            // ⬇️初始化容器
            context.refresh();
    
            // ⬇️销毁容器
            context.close();
        }
    }
    
    

    运行结果:

    image-20220605155124771

  • 结果显示只用config本身被spring管理,在容器中创建了单例。而在Config类中用@Bean标注的方法生成bean1没有生效,这是因为

    容器本身不具备解析@Bean的能力,需要借助BeanFactory 后处理器才能完成这项工作。

  • 改造代码: 为容器注册BeanFactory 后处理器:ConfigurationClassPostProcessor

    @Slf4j
    public class A05 {
        public static void main(String[] args) {
            // ⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
    
            /**
             * 添加BeanFactory 后处理器
             * 可以解析的注解: @ComponentScan @Bean @Import @ImportResource
             */
            context.registerBean(ConfigurationClassPostProcessor.class);
    
            // ⬇️初始化容器
            context.refresh();
    
            // ⬇️销毁容器
            context.close();
        }
    }
    

    再次运行:我们可以看到bean1被创建了。

    image-20220605155731800

  • ConfigurationClassPostProcessor不仅可以识别解析@Bean,它还可以解析@ComponentScan@Import @ImportResource等注解。改造Config类加入@ComponentScan注解。

    @Configuration
    @ComponentScan("com.colin.a05.component")
    public class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }
    }
    

    再次运行A05结果:

    image-20220605160226849

    • 除了bean1,被@Component修饰的bean2,以及被@Controller修饰的bean3都加入了容器,而bean4没有任何注解修饰,就不被解析了。
⑵、演示MapperScannerConfigurer
  • 准备工作 代码结构如下,mapper包下有三个bean,mapper父级目录含有一个配置类,以及测试类A05

    image-20220605163102645

    • Mapper1

      public interface Mapper1 {
      }
      
    • Mapper2

      public interface Mapper2 {
      }
      
    • Mapper3

      public class Mapper3 {
      }
      
    • Config类新增数据库与mybatis的相关配置bean

      import com.alibaba.druid.pool.DruidDataSource;
      import org.mybatis.spring.SqlSessionFactoryBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      import javax.sql.DataSource;
      
      @Configuration
      public class Config {
      
           /**
           * 数据源
           *
           * @param dataSource
           * @return
           */
          @Bean(initMethod = "init")
          public DruidDataSource dataSource() {
              DruidDataSource dataSource = new DruidDataSource();
              dataSource.setUrl("jdbc:mysql://localhost:3306/test");
              dataSource.setUsername("root");
              dataSource.setPassword("root");
              return dataSource;
          }
      
          /**
           * SqlSession 工厂
           *
           * @param dataSource
           * @return
           */
          @Bean
          public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
              SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
              sqlSessionFactoryBean.setDataSource(dataSource);
              return sqlSessionFactoryBean;
          }
      }
      
  • 准备工作完毕,我们开始编写测试类,准备一个干净的容器。并将配置类【Config类】注册到容器中,并进行容器的初始化。

    @Slf4j
    public class A05 {
        public static void main(String[] args) {
            // ⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
            // ⬇️初始化容器
            context.refresh();
            // ⬇️销毁容器
            context.close();
        }
    }
    
    • 运行:结果,只用数据源和SqlSession 工厂在容器中,我们定义的Mapper并不在容器

      image-20220605163718506

  • 首先我们要明确我们写的Mapper是一个接口,接口是不能被创建的,Mybatis会为每个Mapper接口创建响应的代理类,代理类实现了我们定义的接口,所以真正工作的是那个代理类对象。

    MapperFactoryBeanMybatisMapper生成具体对象的工厂,我们可以通过往容器中放入工厂从而得到Mapper代理对象。

    改造Config类手动为Mapper1以及Mapper2创建工厂对象。

    package com.colin.a05;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.colin.a05.mapper.Mapper1;
    import com.colin.a05.mapper.Mapper2;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.mapper.MapperFactoryBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    
    @Configuration
    public class Config {
    
        @Bean(initMethod = "init")
        public DruidDataSource dataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUrl("jdbc:mysql://localhost:3306/test");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            return dataSource;
        }
    
        /**
         * SqlSession 工厂
         *
         * @param dataSource
         * @return
         */
        @Bean
        public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSource);
            return sqlSessionFactoryBean;
        }
    
        /*
         * 能把Mapper生成具体对象的工厂
         * @param sqlSessionFactory
         * @return
         */
    
        @Bean
        public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
            MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
            factory.setSqlSessionFactory(sqlSessionFactory);
            return factory;
        }
    
    
        /*
         * 能把Mapper生成具体对象的工厂
         * @param sqlSessionFactory
         * @return
         */
    
        @Bean
        public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
            MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
            factory.setSqlSessionFactory(sqlSessionFactory);
            return factory;
        }
    
    
    }
    

    运行结果:Mapper1 与 Mapper2被spring管理啦

    image-20220605164454096

    输出Mapper1 与 Mapper2 可以看到它们是代理对象。

    package com.colin.a05;
    
    import com.colin.a05.mapper.Mapper1;
    import com.colin.a05.mapper.Mapper2;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.support.GenericApplicationContext;
    
    @Slf4j
    public class A05 {
        public static void main(String[] args) {
            // ⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
            // ⬇️初始化容器
            context.refresh();
            //获取
            Mapper1 mapper1 = context.getBean(Mapper1.class);
            Mapper2 mapper2 = context.getBean(Mapper2.class);
            System.out.println(mapper1);
            System.out.println(mapper2);
    
            // ⬇️销毁容器
            context.close();
        }
    }
    
    

    image-20220605164713464

  • 但是真实开发中,我们不可能每个Mapper都手动创建它的工厂,我们会通过注解的方式,然后通过BeanFactory 后处理器解析注解生成对应的代理类对象为我们使用。就是把活交给BeanFactory 后处理器。

    改造代码,添加BeanFactory 后处理器MapperScannerConfigurer,添加时需要指明基础包路径。将Config类的MapperFactoryBean注释

    • Config
    package com.colin.a05;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.colin.a05.mapper.Mapper1;
    import com.colin.a05.mapper.Mapper2;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.mapper.MapperFactoryBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    
    @Configuration
    public class Config {
    
     .....
    
        /*
         * 能把Mapper生成具体对象的工厂
         * @param sqlSessionFactory
         * @return
         */
    
        /**@Bean
        public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
            MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
            factory.setSqlSessionFactory(sqlSessionFactory);
            return factory;
        }**/
    
    
        /*
         * 能把Mapper生成具体对象的工厂
         * @param sqlSessionFactory
         * @return
         */
    
        /**@Bean
        public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
            MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
            factory.setSqlSessionFactory(sqlSessionFactory);
            return factory;
        }**/
    
    
    }
    
    • A05
    package com.colin.a05;
    
    import com.colin.a05.mapper.Mapper1;
    import com.colin.a05.mapper.Mapper2;
    import lombok.extern.slf4j.Slf4j;
    import org.mybatis.spring.mapper.MapperScannerConfigurer;
    import org.springframework.context.support.GenericApplicationContext;
    
    @Slf4j
    public class A05 {
        public static void main(String[] args) {
    
    
            // ⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
    
            /**
             * 添加BeanFactory 后处理器
             * 可以解析的注解: @MapperScanner
             */
            context.registerBean(MapperScannerConfigurer.class, bd -> {
                bd.getPropertyValues().add("basePackage","com.colin.a05.mapper");
            });
    
            // ⬇️初始化容器
            context.refresh();
    
            //获取
            Mapper1 mapper1 = context.getBean(Mapper1.class);
            Mapper2 mapper2 = context.getBean(Mapper2.class);
            System.out.println(mapper1);
            System.out.println(mapper2);
    
            // ⬇️销毁容器
            context.close();
        }
    }
    
    

    再次运行:结果是一样的

    image-20220605165744145

⑶、总结
  • ConfigurationClassPostProcessor 可以解析
  • @ComponentScan
  • @Bean
  • @Import
  • @ImportResource
  • MapperScannerConfigurer 可以解析
  • Mapper 接口
收获💡
  1. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
  2. 这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义

②、演示2 - 模拟解析 @ComponentScan
  • 我们手动模拟一下解析 @ComponentScan 新建ResolveComponentScan类,代码步骤如下

    • 1、获取Config类的@ComponentScan注解,使用Spring提供的注解工具类AnnotationUtils

      //1、取得@ComponentScan注解
      /**
       * 使用Spring提供的注解工具类
       */
      ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
      
    • 2、获取basePackages属性,并遍历

       //2、遍历basePackages属性
      for (String basePackage : componentScan.basePackages()) {
          
      }
      
    • 3、获取包指向的类路径并将包路径转换为类路径。(循环体内)

      //3、获取包指向的类路径---》把包路径转换为类路径
      //转换路径 com.colin.a05.component -> classpath*:com/colin/a05/component/**/*.class
      String path = "classpath*:" + basePackage.replace(".", "/") + "/**/*.class";
      log.info("包的类路径:{}", path);
      
    • 4、根据类路径获取资源,其实就是获取这些类,并遍历资源(循环体内)

       Resource[] resources = context.getResources(path);
       for (Resource resource : resources) {
           
       }
      
    • 5、获取资源类的元信息 ,通过spring提供的一个工厂类 CachingMetadataReaderFactory 可以获取类的元信息。(遍历资源循环体内)

      log.info("类资源:{}", resource);
      //5、获取类的元信息
      /**
       * 通过spring提供的一个工厂类
       * CachingMetadataReaderFactory 可以获取类的元信息
       */
      MetadataReader metadataReader = factory.getMetadataReader(resource);
      log.info("类名:{}", metadataReader.getClassMetadata().getClassName());
      log.info("注解信息(是否加了Component):{}", metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName()));
      log.info("注解信息(是否加了Component或者Component派生注解):{}", metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
      
    • 6、根据类的元信息,筛选出我们感兴趣的类(加了Component注解,或者Component注解的派生注解)(遍历资源循环体内)

      if (metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName())                            || metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) {
          
      }
      
    • 7、将这些类加入加入bean工厂的bean定义(遍历资源循环体内)

      //如果资源类加了Component注解,或者Component注解的派生注解 进行处理
      //7、将这些类加入加入bean工厂的bean定义
      AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
              .genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
              .getBeanDefinition();
      
    • 8、生成bean 的名字 使用spring提供的注解bean名字生成器

      AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
      String beanName = generator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());
      
    • 9、放入bean工厂。 将bean注册到bean工厂

      context.getDefaultListableBeanFactory()
              .registerBeanDefinition(beanName, beanDefinition);
      
  • 完整代码:

    package com.colin.a05;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.support.AbstractBeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.context.annotation.AnnotationBeanNameGenerator;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.support.GenericApplicationContext;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.core.io.Resource;
    import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    /*
     * @Author Colin
     * @Description //TODO 模拟@ComponentScan解析
     * @Date 2022/6/5 17:01
     **/
    @Slf4j
    public class ResolveComponentScan {
    
        public static void main(String[] args) throws IOException {
    
            GenericApplicationContext context = new GenericApplicationContext();
    
            //1、取得@ComponentScan注解
            /**
             * 使用Spring提供的注解工具类
             */
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan != null) {
                //标注了ComponentScan注解
                //2、遍历basePackages属性
                for (String basePackage : componentScan.basePackages()) {
                    //3、获取包指向的类路径---》把包路径转换为类路径
                    //转换路径 com.colin.a05.component -> classpath*:com/colin/a05/component/**/*.class
                    String path = "classpath*:" + basePackage.replace(".", "/") + "/**/*.class";
                    log.info("包的类路径:{}", path);
    
                    //4、根据类路径获取资源,其实就是获取这些类
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    Resource[] resources = context.getResources(path);
                    for (Resource resource : resources) {
                        log.info("类资源:{}", resource);
                        //5、获取类的元信息
                        /**
                         * 通过spring提供的一个工厂类
                         * CachingMetadataReaderFactory 可以获取类的元信息
                         */
                        MetadataReader metadataReader = factory.getMetadataReader(resource);
                        log.info("类名:{}", metadataReader.getClassMetadata().getClassName());
                        log.info("注解信息(是否加了Component):{}", metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName()));
                        log.info("注解信息(是否加了Component或者Component派生注解):{}", metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
    
                        /**
                         * 6、根据类的元信息,筛选出我们感兴趣的类(加了Component注解,或者Component注解的派生注解)
                         */
                        if (metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName())
                                || metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) {
                            //如果资源类加了Component注解,或者Component注解的派生注解 进行处理
                            //7、将这些类加入加入bean工厂的bean定义
                            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                                    .genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
                                    .getBeanDefinition();
    
                            //8、生成bean 的名字 使用spring提供的注解bean名字生成器
                            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                            String beanName = generator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());
    
                            //9、放入bean工厂。 将bean注册到bean工厂
                            context.getDefaultListableBeanFactory()
                                    .registerBeanDefinition(beanName, beanDefinition);
                            log.info("==============放入beanfactory===================>>");
    
                        }
                        log.info("=============================================>>");
    
                    }
    
                    // ⬇️初始化容器
                    context.refresh();
    
                    // ⬇️销毁容器
                    context.close();
                }
    
    
            }
    
        }
    }
    

    运行结果:

    image-20220605174106480

⑴、总结
  1. Spring 操作元数据的工具类 CachingMetadataReaderFactory
  2. 通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
  3. 通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
  4. 解析元数据是基于 ASM 技术
③、演示3 - 模拟解析 @Bean
  • 我们手动模拟一下解析 @Bean 新建ResolveBean类,代码步骤如下

    • 1、将配置类注册到bean工厂(后续会用到)

      /**
       * 1、将配置类注册到bean工厂
       */
      GenericApplicationContext context = new GenericApplicationContext();
      context.registerBean("config",Config.class);
      
    • 2、读取配置类的元信息,使用spring提供的一个工厂类CachingMetadataReaderFactory

      /**
       * 2、读取配置类的元信息,
       * 使用spring提供的一个工厂类CachingMetadataReaderFactory
       */
      CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
      MetadataReader metadataReader = factory.getMetadataReader(Config.class.getName());
      
    • 3、获取所有被@Bean 注解标志的方法

       Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
      
    • 4、遍历被@bean 注解标志的方法,将这些方法转化为bean的定义放入bean工厂

      for (MethodMetadata annotatedMethod : annotatedMethods) {
          log.info("annotatedMethod:{}", annotatedMethod);
           。。。。。
      }
      
      • 4.1 解析注解的属性, 以initMethod属性为例

        //将@bean 注解标志的方法(spring会将此方法作为工厂方法)
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .genericBeanDefinition();
        //4.1 解析注解的属性, 以initMethod属性为例
        String initMethod = annotatedMethod.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
        if (initMethod.length() > 0) {
            beanDefinitionBuilder.setInitMethodName(initMethod);
            log.info("initMethod:{}", initMethod);
        }
        
      • 4.2 完善BeanDefinition

        //4.2 完善BeanDefinition
        /**
         * 因为@bean 注解标志的方法(spring会将此方法作为工厂方法),
         * 所以我们要设置工厂方法相关的bean定义
         *
         *  annotatedMethod.getMethodName(): 工厂方法的名字
         *  config: 工厂类的bean名字 -- 工厂类必须已经存在 所以要先把Config注册成bean
         */
        beanDefinitionBuilder.setFactoryMethodOnBean(annotatedMethod.getMethodName(), "config");
        
      • 4.4 注册到BeanFactory

        context.getDefaultListableBeanFactory()
                    .registerBeanDefinition(annotatedMethod.getMethodName(), beanDefinitionBuilder.getBeanDefinition());
        

      运行代码:发现出错,SqlSessionFactoryBean无法创建。

      image-20220605180633085
      分析问题:我们查看SqlSessionFactoryBean的方法定义发现这个方法是有参数的。而我们定义BeanDefinition对有参数的方法并没有处理。

      /**
       * SqlSession 工厂
       *
       * @param dataSource
       * @return
       */
      @Bean
      public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
          SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
          sqlSessionFactoryBean.setDataSource(dataSource);
          return sqlSessionFactoryBean;
      }
      

      解决方法,在4.2与4.4步之间加上4.3步,

      • 4.3加入自动装配

         beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
        

      再次运行:错误消失了。

      image-20220605181358290

  • 完整代码:

    package com.colin.a05;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.support.AbstractBeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.support.GenericApplicationContext;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.type.MethodMetadata;
    import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    import org.springframework.core.type.classreading.MetadataReader;
    
    import java.io.IOException;
    import java.util.Set;
    
    /*
     * @Author Colin
     * @Description //TODO 模拟@Bean解析
     * @Date 2022/6/5 17:01
     **/
    @Slf4j
    public class ResolveBean {
    
        public static void main(String[] args) throws IOException {
    
            /**
             * 1、将配置类注册到bean工厂
             */
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
    
            /**
             * 2、读取配置类的元信息,
             * 使用spring提供的一个工厂类CachingMetadataReaderFactory
             */
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            MetadataReader metadataReader = factory.getMetadataReader(Config.class.getName());
    
            /**
             * 3、获取所有被@bean 注解标志的方法
             */
            Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
    
            /**
             * 4、遍历被@bean 注解标志的方法,将这些方法
             * 转化为bean的定义放入bean工厂
             */
            for (MethodMetadata annotatedMethod : annotatedMethods) {
                log.info("annotatedMethod:{}", annotatedMethod);
                //将@bean 注解标志的方法(spring会将此方法作为工厂方法)
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                        .genericBeanDefinition();
                //4.1 解析注解的属性, 以initMethod属性为例
                String initMethod = annotatedMethod.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
                if (initMethod.length() > 0) {
                    beanDefinitionBuilder.setInitMethodName(initMethod);
                    log.info("initMethod:{}", initMethod);
                }
                //4.2 完善BeanDefinition
                /**
                 * 因为@bean 注解标志的方法(spring会将此方法作为工厂方法),
                 * 所以我们要设置工厂方法相关的bean定义
                 *
                 *  annotatedMethod.getMethodName(): 工厂方法的名字
                 *  config: 工厂类的bean名字
                 */
                beanDefinitionBuilder.setFactoryMethodOnBean(annotatedMethod.getMethodName(), "config");
    
                //4.3加入自动装配
                /**
                 * 用来解决那些有参数的工厂方法,让其参数可以自动注入
                 * 比如:sqlSessionFactoryBean这个方法
                 *    @Bean
                 *    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
                 *      SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
                 *      sqlSessionFactoryBean.setDataSource(dataSource);
                 *      return sqlSessionFactoryBean;
                 *  }
                 */
    
                beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    
                //4.4 注册到BeanFactory
                context.getDefaultListableBeanFactory()
                        .registerBeanDefinition(annotatedMethod.getMethodName(), beanDefinitionBuilder.getBeanDefinition());
            }
    
            // ⬇️初始化容器
            context.refresh();
    
            // ⬇️销毁容器
            context.close();
        }
    
    
    }
    
    

    Config:

    package com.colin.a05;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    
    @Configuration
    @ComponentScan("com.colin.a05.component")
    public class Config {
    
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }
    
        @Bean(initMethod = "init")
        public DruidDataSource dataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUrl("jdbc:mysql://localhost:3306/test");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            return dataSource;
        }
    
        /**
         * SqlSession 工厂
         *
         * @param dataSource
         * @return
         */
        @Bean
        public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSource);
            return sqlSessionFactoryBean;
        }
    
        /*
         * 能把Mapper生成具体对象的工厂
         * @param sqlSessionFactory
         * @return
         */
    
        /*@Bean
        public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
            MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
            factory.setSqlSessionFactory(sqlSessionFactory);
            return factory;
        }*/
    
    
        /*
         * 能把Mapper生成具体对象的工厂
         * @param sqlSessionFactory
         * @return
         */
    
      /*  @Bean
        public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
            MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
            factory.setSqlSessionFactory(sqlSessionFactory);
            return factory;
        }*/
    
    
    }
    
④、演示4 - 模拟解析 Mapper 接口
  • 我们手动模拟一下解析 Mapper接口 新建ResolveMapper类,代码步骤如下

    • 1、获取指定路径所有类资源

      //1、获取指定路径所有类资源
      Resource[] resources = context.getResources("classpath:com/colin/a05/mapper/**/*.class");
      
    • 2、遍历所有类资源

      //2、遍历所有类资源
      for (Resource resource : resources) {
        log.info("类资源:{}", resource);
          ......
      }
      
    • 3、获取类资源的元信息 使用spring工厂类CachingMetadataReaderFactory 可以获取类的元信息(循环体内)

       //3、获取类资源的元信息 使用spring工厂类CachingMetadataReaderFactory 可以获取类的元信息
      CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
      MetadataReader metadataReader = factory.getMetadataReader(resource);
      
    • 4、判断类是不是接口(循环体内)

      if (metadataReader.getClassMetadata().isInterface()) {
          
      }
      
    • 5、是接口的话,定义BeanDefinition 根据接口定义出 MapperFactoryBean(把Mapper生成对象的工厂bean)

      //是接口
      //5、定义BeanDefinition 根据接口定义出 MapperFactoryBean(把Mapper生成对象的工厂bean)
      AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
              .genericBeanDefinition(MapperFactoryBean.class)
              //设置构造
              .addConstructorArgValue(metadataReader.getClassMetadata().getClassName())
              .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
              .getBeanDefinition();
      
    • 6、生成bean的名字 使用spring提供的注解bean名字生成器

      注意事项

      • 注意这里要根据mapper接口生成bean的名字
      • 如果根据MapperFactoryBean工厂类生成名字,多个mapper接口都会是MapperFactoryBean类型 名字都是mapperFactoryBean
      • 相同名字的bean会被覆盖 所以需要用mapper接口类型生成bean的名字
      //6、生成bean的名字 使用spring提供的注解bean名字生成器
          /**
           * 注意这里要根据mapper接口生成bean的名字
           * 如果根据MapperFactoryBean工厂类生成名字,
           * 多个mapper接口都会是MapperFactoryBean类型 名字都是mapperFactoryBean
           * 相同名字的bean会被覆盖 所以需要用mapper接口类型生成bean的名字
           */
      AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
      
      AbstractBeanDefinition bd2 = BeanDefinitionBuilder
              .genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
              .getBeanDefinition();
      String beanName = generator.generateBeanName(bd2, context.getDefaultListableBeanFactory());
      
    • 7、 将bean定义注册到BeanFactory

      //7. 将bean定义注册到BeanFactory
      context.registerBeanDefinition(beanName, beanDefinition);
      
  • 完整代码:

    package com.colin.a05;
    
    import lombok.extern.slf4j.Slf4j;
    import org.mybatis.spring.mapper.MapperFactoryBean;
    import org.springframework.beans.factory.support.AbstractBeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.context.annotation.AnnotationBeanNameGenerator;
    import org.springframework.context.annotation.ConfigurationClassPostProcessor;
    import org.springframework.context.support.GenericApplicationContext;
    import org.springframework.core.io.Resource;
    import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    import org.springframework.core.type.classreading.MetadataReader;
    
    import java.io.IOException;
    
    /*
     * @Author Colin
     * @Description //TODO 模拟@Mapper解析
     * @Date 2022/6/5 17:01
     **/
    @Slf4j
    public class ResolveMapper {
    
        public static void main(String[] args) throws IOException {
    
    
            GenericApplicationContext context = new GenericApplicationContext();
    
    
            /**
             * 准备工作, mapper依赖于数据库与mybatis所有先将相关需要的bean注册到beanfactory
             */
            context.registerBean("config", Config.class);
    
            /**
             * 添加BeanFactory 后处理器
             * 可以解析的注解: @ComponentScan @Bean @Import @ImportResource
             */
            context.registerBean(ConfigurationClassPostProcessor.class);
    
            //准备工作完毕==================================
    
            //1、获取指定路径所有类资源
            Resource[] resources = context.getResources("classpath:com/colin/a05/mapper/**/*.class");
    
            //2、遍历所有类资源
            for (Resource resource : resources) {
                log.info("类资源:{}", resource);
                //3、获取类资源的元信息 使用spring工厂类CachingMetadataReaderFactory 可以获取类的元信息
                CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                MetadataReader metadataReader = factory.getMetadataReader(resource);
    
                //4、判断类是不是接口
                if (metadataReader.getClassMetadata().isInterface()) {
                    //是接口
                    //5、定义BeanDefinition 根据接口定义出 MapperFactoryBean(把Mapper生成对象的工厂bean)
                    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                            .genericBeanDefinition(MapperFactoryBean.class)
                            //设置构造
                            .addConstructorArgValue(metadataReader.getClassMetadata().getClassName())
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();
    
                    //6、生成bean的名字 使用spring提供的注解bean名字生成器
                    /**
                     * 注意这里要根据mapper接口生成bean的名字
                     * 如果根据MapperFactoryBean工厂类生成名字,
                     * 多个mapper接口都会是MapperFactoryBean类型 名字都是mapperFactoryBean
                     * 相同名字的bean会被覆盖 所以需要用mapper接口类型生成bean的名字
                     */
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
    
                    AbstractBeanDefinition bd2 = BeanDefinitionBuilder
                            .genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
                            .getBeanDefinition();
                    String beanName = generator.generateBeanName(bd2, context.getDefaultListableBeanFactory());
    
                    //7. 将bean定义注册到BeanFactory
                    context.registerBeanDefinition(beanName, beanDefinition);
    
                }
            }
    
    
            // ⬇️初始化容器
            context.refresh();
    
            // ⬇️销毁容器
            context.close();
        }
    
    
    }
    

    运行结果:

    image-20220605183909503

⑴、总结
  1. Mapper 接口被 Spring 管理的本质:实际根据Mapper接口定义 MapperFactoryBean工厂,并将工厂 注册到容器中
  2. Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名
⑤、根据演示自定义BeanFactory后处理器
  • 自定义的处理ComponentScan的BeanFactory 后处理器

    • 定义ComponentScanPostProcessor类实现BeanDefinitionRegistryPostProcessor接口根据演示2 实现相关逻辑

      package com.colin.a05;
      
      import org.springframework.beans.BeansException;
      import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
      import org.springframework.beans.factory.support.AbstractBeanDefinition;
      import org.springframework.beans.factory.support.BeanDefinitionBuilder;
      import org.springframework.beans.factory.support.BeanDefinitionRegistry;
      import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
      import org.springframework.context.annotation.AnnotationBeanNameGenerator;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.core.annotation.AnnotationUtils;
      import org.springframework.core.io.Resource;
      import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
      import org.springframework.core.type.AnnotationMetadata;
      import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
      import org.springframework.core.type.classreading.MetadataReader;
      import org.springframework.stereotype.Component;
      
      import java.io.IOException;
      
      /**
       * 自定义的处理ComponentScan的BeanFactory 后处理器
       */
      public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
          @Override
          public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
             // context.refresh
          }
      
          @Override
          public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
              try {
                  ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
                  if (componentScan != null) {
                      for (String p : componentScan.basePackages()) {
                          System.out.println(p);
                          // com.colin.a05.component -> classpath*:com/colin/a05/component/**/*.class
                          String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
                          System.out.println(path);
                          CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                          Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                          AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                          for (Resource resource : resources) {
                              // System.out.println(resource);
                              MetadataReader reader = factory.getMetadataReader(resource);
                              // System.out.println("类名:" + reader.getClassMetadata().getClassName());
                              AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                              // System.out.println("是否加了 @Component:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                              // System.out.println("是否加了 @Component 派生:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
                              if (annotationMetadata.hasAnnotation(Component.class.getName())
                                      || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
                                  AbstractBeanDefinition bd = BeanDefinitionBuilder
                                          .genericBeanDefinition(reader.getClassMetadata().getClassName())
                                          .getBeanDefinition();
                                  String name = generator.generateBeanName(bd, beanFactory);
                                  beanFactory.registerBeanDefinition(name, bd);
                              }
                          }
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      
      
  • 自定义的处理@Bean的BeanFactory 后处理器

    • 定义BeanPostProcessor类实现BeanDefinitionRegistryPostProcessor接口根据演示3 实现相关逻辑

      package com.colin.a05;
      
      import org.springframework.beans.BeansException;
      import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
      import org.springframework.beans.factory.support.AbstractBeanDefinition;
      import org.springframework.beans.factory.support.BeanDefinitionBuilder;
      import org.springframework.beans.factory.support.BeanDefinitionRegistry;
      import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
      import org.springframework.context.annotation.Bean;
      import org.springframework.core.io.ClassPathResource;
      import org.springframework.core.type.MethodMetadata;
      import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
      import org.springframework.core.type.classreading.MetadataReader;
      
      import java.io.IOException;
      import java.util.Set;
      
      /*
       * @Author Colin
       * @Description //TODO 自定义的处理@Bean的BeanFactory 后处理器
       * @Date 2022/6/5 18:48
       **/
      public class BeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
          @Override
          public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
      
          }
      
          @Override
          public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
              try {
                  CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                  MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/colin/a05/Config.class"));
                  Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
                  for (MethodMetadata method : methods) {
                      System.out.println(method);
                      String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
                      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                      builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                      builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                      if (initMethod.length() > 0) {
                          builder.setInitMethodName(initMethod);
                      }
                      AbstractBeanDefinition bd = builder.getBeanDefinition();
                      beanFactory.registerBeanDefinition(method.getMethodName(), bd);
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      
      
  • 自定义的处理Mapper接口的BeanFactory 后处理器

    • 定义MapperPostProcessor类实现BeanDefinitionRegistryPostProcessor接口根据演示4 实现相关逻辑

      package com.colin.a05;
      
      import org.mybatis.spring.mapper.MapperFactoryBean;
      import org.springframework.beans.BeansException;
      import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
      import org.springframework.beans.factory.support.AbstractBeanDefinition;
      import org.springframework.beans.factory.support.BeanDefinitionBuilder;
      import org.springframework.beans.factory.support.BeanDefinitionRegistry;
      import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
      import org.springframework.context.annotation.AnnotationBeanNameGenerator;
      import org.springframework.core.io.Resource;
      import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
      import org.springframework.core.type.ClassMetadata;
      import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
      import org.springframework.core.type.classreading.MetadataReader;
      
      import java.io.IOException;
      
      /*
       * @Author Colin
       * @Description //TODO 自定义的处理 Mapper接口的BeanFactory 后处理器
       * @Date 2022/6/5 18:50
       **/
      public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
      
          @Override
          public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
              try {
                  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
                  Resource[] resources = resolver.getResources("classpath:com/colin/a05/mapper/**/*.class");
                  AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                  CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                  for (Resource resource : resources) {
                      MetadataReader reader = factory.getMetadataReader(resource);
                      ClassMetadata classMetadata = reader.getClassMetadata();
                      if (classMetadata.isInterface()) {
                          AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                                  .addConstructorArgValue(classMetadata.getClassName())
                                  .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                                  .getBeanDefinition();
                          AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                          String name = generator.generateBeanName(bd2, beanFactory);
                          beanFactory.registerBeanDefinition(name, bd);
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
          }
      
          @Override
          public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      
          }
      }
      
⑴、测试
  • 创建A05_1测试类如下:

    package com.colin.a05;
    
    import com.colin.a05.mapper.Mapper1;
    import com.colin.a05.mapper.Mapper2;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.support.GenericApplicationContext;
    
    @Slf4j
    public class A05_1 {
        public static void main(String[] args) {
    
    
            // ⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
    
            //⬇️添加我们自己的BeanFactory后处理器
            context.registerBean(ComponentScanPostProcessor.class); // 解析 @ComponentScan
            context.registerBean(BeanPostProcessor.class); // 解析 @Bean
            context.registerBean(MapperPostProcessor.class); // 解析 Mapper 接口
            // ⬇️初始化容器
            context.refresh();
    
            //获取
            Mapper1 mapper1 = context.getBean(Mapper1.class);
            Mapper2 mapper2 = context.getBean(Mapper2.class);
            log.info(mapper1.toString());
            log.info(mapper2.toString());
    
            // ⬇️销毁容器
            context.close();
        }
    }
    
    
  • 运行结果

    image-20220605185459836

a06Aware 接口及 InitializingBean 接口

①、演示1 - Aware 接口及 InitializingBean 接口
  • 准备工作: 创建一个MyBean类,实现常用的Aware 接口以及 InitializingBean 接口,并在响应的方法中执行打印。

    package com.colin.a06;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.BeanFactoryAware;
    import org.springframework.beans.factory.BeanNameAware;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.EmbeddedValueResolverAware;
    import org.springframework.util.StringValueResolver;
    
    /**
     * @Author Colin
     * @Description 测试Aware接口及 InitializingBean 接口
     * @Date 2022/6/6 10:15
     */
    @Slf4j
    public class MyBean implements BeanNameAware, InitializingBean, BeanFactoryAware, ApplicationContextAware, EmbeddedValueResolverAware {
    
        /**
         * BeanNameAware 接口抽象方法
         * 会将bean的名字注入
         *
         * @param name
         */
        @Override
        public void setBeanName(String name) {
            log.info("MyBean在BeanFactory的名字为:{}", name);
        }
    
        /**
         * InitializingBean接口 提供了一种【内置】的初始化手段
         * 会在扩展功能后被调用 (如依赖注入扩展、初始化方法 @PostConstruct扩展)
         *
         * @throws Exception
         */
        @Override
        public void afterPropertiesSet() throws Exception {
            log.info("InitializingBean接口 【内置】的初始化手段");
        }
    
        /**
         * BeanFactoryAware 接口
         * 会为bean注入BeanFactory 容器
         *
         * @param beanFactory
         * @throws BeansException
         */
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            log.info("我是BeanFactoryAware 接口 为bean注入BeanFactory :{}", beanFactory);
        }
    
        /**
         * ApplicationContextAware 接口
         * 会为bean注入ApplicationContext 容器
         *
         * @param applicationContext
         * @throws BeansException
         */
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            log.info("我是ApplicationContextAware 接口 为bean注入applicationContext :{}", applicationContext);
        }
    
        /**
         * EmbeddedValueResolverAware接口
         * 会为bean 注入 ${} 解析器
         *
         * @param resolver
         */
        @Override
        public void setEmbeddedValueResolver(StringValueResolver resolver) {
            log.info("我是EmbeddedValueResolverAware接口 为bean注入 ${} 解析器 解析器:{}", resolver);
            String value = resolver.resolveStringValue("${JAVA_HOME}");
            log.info("解析 ${JAVA_HOME}的值为:{}", value);
        }
    }
    
    
  • 编写测试类 A06,准备一个干净的spring容器,并将MyBean注册到容器中,执行容器的初始化方法。

    package com.colin.a06;
    import org.springframework.context.support.GenericApplicationContext;
    public class A06 {
        public static void main(String[] args) {
    
            //1、准备一个干净的容器
            GenericApplicationContext context = new GenericApplicationContext();
    
            //2、将MyBean注册到容器中
            context.registerBean(MyBean.class);
    
            //3、初始化容器
            context.refresh();
    
            //4、关闭容器
            context.close();
        }
    }
    
  • 运行结果:

    image-20220606103749774

⑴、结果分析
  • Aware 接口提供了一种【内置】 的注入手段,例如

  • BeanNameAware 注入 bean 的名字

  • BeanFactoryAware 注入 BeanFactory 容器

  • ApplicationContextAware 注入 ApplicationContext 容器

  • EmbeddedValueResolverAware 注入 ${} 解析器

  • InitializingBean 接口提供了一种【内置】的初始化手段

②、演示2 - 扩展功能失效问题
  • 演示1中我们可以看到Aware 接口可以为bean注入各种各样其他的bean,InitializingBean 接口可以提供初始化功能。

    但这些提供的功能都可以被扩展代替,比如Aware 接口的功能可以被@Autowired代替,InitializingBean 接口可以被@PostConstruct代替。

    那他们存在的意义在哪呢? 我们来探究一下。

⑴、功能替代以及扩展失效演示
  • 演示代替功能:创建一个MyBean2类,实现@Autowired以及@PostConstruct代替常用的Aware 接口以及 InitializingBean 接口。

    package com.colin.a06;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    
    import javax.annotation.PostConstruct;
    
    /**
     * @Author Colin
     * @Description 测试Aware接口及 InitializingBean 接口
     * @Date 2022/6/6 10:15
     */
    @Slf4j
    public class MyBean2 {
    
    
        /**
         * @throws Exception
         * @PostConstruct初始化调用
         */
        @PostConstruct
        public void afterPropertiesSet() throws Exception {
            log.info(" @PostConstruct 初始化手段");
        }
    
        /**
         * Autowired注入BeanFactory 容器
         *
         * @param beanFactory
         * @throws BeansException
         */
        @Autowired
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            log.info("Autowired注入BeanFactory 容器 :{}", beanFactory);
        }
    
        /**
         * Autowired注入ApplicationContext 容器
         *
         * @param applicationContext
         * @throws BeansException
         */
        @Autowired
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            log.info("Autowired注入ApplicationContext 容器 :{}", applicationContext);
        }
    
    }
    
    • 编写测试类A06_1 ,准备一个干净的spring容器,注册解析相应注解的bean后处理器,并将MyBean2注册到容器中,执行容器的初始化方法。
    package com.colin.a06;
    
    
    import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
    import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
    import org.springframework.context.support.GenericApplicationContext;
    
    public class A06_1 {
        public static void main(String[] args) {
    
            //1、准备一个干净的容器
            GenericApplicationContext context = new GenericApplicationContext();
    
            //2、将MyBean注册到容器中
            context.registerBean(MyBean2.class);
    
            //3、加入一些bean的后处理器
            /**
             * 为context提供处理@Autowired注解的能力
             */
            context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
    
            /**
             * 为context提供处理@PostConstruct注解的能力
             */
            context.registerBean(CommonAnnotationBeanPostProcessor.class);
    
            //4、初始化容器
            context.refresh();
    
            //5、关闭容器
            context.close();
        }
    }
    
    
    • 运行结果:
      image-20220606110626854
  • @Autowired 的解析需要用到 bean 后处理器, 属于扩展功能,而 Aware 接口属于内置功能, 不加任何扩展, Spring 就能识别。某些情况下, 扩展功能会失效, 而内置功能不会失效。 下面演示@Autowired失效的情况。

    • 编写一个配置类MyConfig,里面使用@Autowired以及@PostConstruct,并且使用@Bean 为容器添加一个BeanFactory后处理器,代码如下:

      package com.colin.a06;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      import javax.annotation.PostConstruct;
      
      @Slf4j
      @Configuration
      public class MyConfig {
      
          @Autowired
          public void setApplicationContext(ApplicationContext applicationContext) {
              log.info("注入 ApplicationContext");
          }
      
          @PostConstruct
          public void init() {
              log.info("初始化");
          }
      
          /**
           * beanFactory 后处理器 这里会导致@Autowired,@value...等扩展会失效
           *
           * @return
           */
          @Bean
          public BeanFactoryPostProcessor processor() {
              return beanFactory -> log.info("执行processor,补充bean的定义");
          }
      }
      
      
    • 编写测试类A06_2,准备一个干净的spring容器,注册解析相应注解的后处理器,并将MyConfig注册到容器中,执行容器的初始化方法。

      package com.colin.a06;
      
      
      import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
      import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
      import org.springframework.context.annotation.ConfigurationClassPostProcessor;
      import org.springframework.context.support.GenericApplicationContext;
      
      public class A06_2 {
          public static void main(String[] args) {
      
              //1、准备一个干净的容器
              GenericApplicationContext context = new GenericApplicationContext();
      
              //2、将MyConfig注册到容器中
              context.registerBean(MyConfig.class);
      
              //3、加入一些bean工厂的后处理器
              /**
               *可以解析的注解: @ComponentScan @Bean @Import @ImportResource
               */
              context.registerBean(ConfigurationClassPostProcessor.class);
      
      
              //4、加入一些bean的后处理器
              /**
               * 为context提供处理@Autowired注解的能力
               */
              context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
      
              /**
               * 为context提供处理@PostConstruct注解的能力
               */
              context.registerBean(CommonAnnotationBeanPostProcessor.class);
      
              //5、初始化容器
              context.refresh();
      
              //6、关闭容器
              context.close();
          }
      }
      
    • 运行结果:只执行了@Bean修饰的方法,@Autowired以及@PostConstruct都没有打印,功能失效了。

      image-20220606112322174

⑵、扩展失效的原因分析
  • @Autowired 失效分析

    • Java 配置类不包含 BeanFactoryPostProcessor 的情况.spring初始化容器时步骤大致如下。

      ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类 1. 执行 BeanFactoryPostProcessor 2. 注册 BeanPostProcessor 3. 创建和初始化 3.1 依赖注入扩展(如 @Value 和 @Autowired) 3.2 初始化扩展(如 @PostConstruct) 3.3 执行 Aware 及 InitializingBean 3.4 创建成功 ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类
      • 1、首先执行bean工厂的后处理器,完善一些bean的定义。
      • 2、准备好相关的bean后处理器,因为创建bean的时候,可能要对bean做一些扩展,从而完善bean的功能。
      • 3、开始根据bean的定义创建bean(反射调用构造)
      • 4、执行扩展,根据bean后处理器器排序规则解析bean相关注解对bean做一些扩展。(先进行依赖注入,在进行初始化扩展)
      • 5、执行内置的Aware接口以及 InitializingBean对bean做一些扩展
      • 6、创建成功
    • Java 配置类包含 BeanFactoryPostProcessor 的情况,

      因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,

      而此时的 BeanPostProcessor 还未准备好,导致 @Autowired等注解失效

      ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类 3. 创建和初始化 3.1 执行 Aware 及 InitializingBean 3.2 创建成功 1. 执行 BeanFactoryPostProcessor 2. 注册 BeanPostProcessor ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类
      • 1、因为java类中有bean工厂的后处理器,而bean工厂的后处理一开始就需要,因为要把bean的定义先准备好,所以为了获得完整的bean工厂的后处理器,必须先创建java类(因为@Bean注解修饰的方法会变成工厂方法,java类是工厂bean),执行java类的创建
      • 2、执行内置的Aware接口以及 InitializingBean对bean做一些扩展
      • 3、创建成功
      • 4、执行bean工厂的后处理器,完善一些bean的定义
      • 5、准备好相关的bean后处理器
⑶、扩展失效的解决方法
  • 用内置依赖注入和初始化取代扩展依赖注入和初始化 ,效果代码请看演示1

  • 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建,比如改造演示2中MyConfig的代码变成MyConfig1如下所示,将processor()方法变成静态方法。

    package com.colin.a06;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    
    import javax.annotation.PostConstruct;
    
    @Slf4j
    public class MyConfig1 {
    
        @Autowired
        public void setApplicationContext(ApplicationContext applicationContext) {
            log.info("注入 ApplicationContext");
        }
    
        @PostConstruct
        public void init() {
            log.info("初始化");
        }
    
        /**
         * beanFactory 后处理器 这里会导致@Autowired,@value...等扩展会失效
         *
         * @return
         */
        @Bean
        public static BeanFactoryPostProcessor processor() {
            return beanFactory -> log.info("执行processor,补充bean的定义");
        }
    }
    
    

    测试:结果就正常了。

    image-20220606114556660

⑷、总结
  • Aware 接口提供了一种【内置】 的注入手段,例如

  • BeanNameAware 注入 bean 的名字

  • BeanFactoryAware 注入 BeanFactory 容器

  • ApplicationContextAware 注入 ApplicationContext 容器

  • EmbeddedValueResolverAware 注入 ${} 解析器

  • InitializingBean 接口提供了一种【内置】的初始化手段

  • 对比

  • 内置的注入和初始化不受扩展功能的影响,总会被执行

  • 而扩展功能受某些情况影响可能会失效

  • 因此 Spring 框架内部的类常用内置注入和初始化

  • 解决扩展功能失效

  • 用内置依赖注入和初始化取代扩展依赖注入和初始化

  • 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建

a07、初始化与销毁

①、演示1 - 初始化顺序
  • Spring 提供了多种初始化手段,除@PostConstruct@Bean(initMethod) 之外,还可以实现InitializingBean接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是什么样的呢?

    • 创建Bean1

      package com.colin.a07;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.InitializingBean;
      import javax.annotation.PostConstruct;
      @Slf4j
      public class Bean1 implements InitializingBean {
      
      
          @PostConstruct
          public void init() {
              log.info("使用@PostConstruct 注解 初始化1");
          }
      
          @Override
          public void afterPropertiesSet() throws Exception {
              log.info("实现 InitializingBean 接口,初始化方法2");
          }
      
          public void init3() {
              log.debug("初始化3");
          }
      }
      
      
    • 创建测试类A07 并在类中使用@Bean注解指明bean1的初始化方法并将bean1加入容器,准备一个干净的容器,加入一些用到的后处理器,初始化容器。

      package com.colin.a07;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
      import org.springframework.context.annotation.ConfigurationClassPostProcessor;
      import org.springframework.context.support.GenericApplicationContext;
      
      public class A07 {
          public static void main(String[] args) {
              //1、准备一个干净的容器
              GenericApplicationContext context = new GenericApplicationContext();
      
              //2、将A07注册到容器中
              context.registerBean(A07.class);
      
              //3、加入一些bean工厂的后处理器
              /**
               *可以解析的注解: @ComponentScan @Bean @Import @ImportResource
               */
              context.registerBean(ConfigurationClassPostProcessor.class);
      
              //4、加入一些bean的后处理器
              /**
               * 为context提供处理@PostConstruct注解的能力
               */
              context.registerBean(CommonAnnotationBeanPostProcessor.class);
      
              //5、初始化容器
              context.refresh();
      
              //6、关闭容器
              context.close();
          }
      
          @Bean(initMethod = "init3")
          public Bean1 bean() {
              return new Bean1();
          }
      }
      
      
    • 运行测试:可以看到执行顺序: @PostConstruct >>>>InitializingBean>>>>@Bean(initMethod)

      image-20220606135540171

②、演示2 - 销毁顺序
  • 同样的Spring也 提供了多种销毁手段,除@PreDestroy@Bean(destroyMethod) 之外,还可以实现DisposableBean接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个销毁方法,那么它们的执行顺序是什么样的呢?

    • 创建Bean2

      package com.colin.a07;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.DisposableBean;
      
      import javax.annotation.PreDestroy;
      
      @Slf4j
      public class Bean2 implements DisposableBean {
      
      
          @PreDestroy
          public void preDestroy() {
              log.info("使用@PreDestroy 注解 销毁方法1");
          }
      
          @Override
          public void destroy() throws Exception {
              log.info("实现 DisposableBean 接口,销毁方法2");
          }
      
      
          public void destroy3() {
              log.debug("销毁方法3");
          }
      }
      
      
    • 创建测试类A07_1并在类中使用@Bean注解指明bean2的销毁方法并将bean2加入容器,准备一个干净的容器,加入一些用到的后处理器,初始化容器。

      package com.colin.a07;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
      import org.springframework.context.annotation.ConfigurationClassPostProcessor;
      import org.springframework.context.support.GenericApplicationContext;
      
      public class A07_1 {
          public static void main(String[] args) {
              //1、准备一个干净的容器
              GenericApplicationContext context = new GenericApplicationContext();
      
              //2、将A07注册到容器中
              context.registerBean(A07_1.class);
      
              //3、加入一些bean工厂的后处理器
              /**
               *可以解析的注解: @ComponentScan @Bean @Import @ImportResource
               */
              context.registerBean(ConfigurationClassPostProcessor.class);
      
              //4、加入一些bean的后处理器
              /**
               * 为context提供处理@PostConstruct注解的能力
               */
              context.registerBean(CommonAnnotationBeanPostProcessor.class);
      
      
              //5、初始化容器
              context.refresh();
      
              //6、关闭容器
              context.close();
          }
      
          @Bean(destroyMethod = "destroy3")
          public Bean2 bean() {
              return new Bean2();
          }
      }
      
      
    • 运行测试:可以看到执行顺序: @PreDestroy >>>>DisposableBean>>>>@Bean(destroyMethod)

      image-20220606140329465

③、总结
  • 初始化方法,它们的执行顺序

  • @PostConstruct 标注的初始化方法

  • InitializingBean 接口的初始化方法

  • @Bean(initMethod) 指定的初始化方法

  • 销毁方法,它们的执行顺序

  • @PreDestroy 标注的销毁方法

  • DisposableBean 接口的销毁方法

  • @Bean(destroyMethod) 指定的销毁方法

a08、bean的作用域(Scope

  • 总述

    在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope

    • singleton,容器启动时创建(未设置延迟,即未设置延迟加载),容器关闭时销毁
    • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
    • request,每次请求用到此 bean 时创建,请求结束时销毁
    • session,每个会话用到此 bean 时创建,会话结束时销毁
    • application,web 容器用到此 bean 时创建,容器停止时销毁

    有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃

①、演示1 - request, session, application 作用域
  • 创建响应演示代码,代码结构如下:

    image-20220606143637029

    • 因为测试需要用到web 环境,使用SpringBoot快速构建web环境,A08为启动类
    • BeanForRequest、BeanForSession、BeanForApplication为我们自定义的bean,并指定相应的作用域
    • MyController是我们的控制器类
  • 具体代码如下:

    • A08

      package com.colin.a08;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      
      @SpringBootApplication
      public class A08 {
      
          public static void main(String[] args) {
              SpringApplication.run(A08.class, args);
          }
      }
      
      
    • BeanForRequest

      package com.colin.a08;
      
      import org.springframework.context.annotation.Scope;
      import org.springframework.stereotype.Component;
      
      /**
       * 作用域在request的bean
       */
      @Scope("request")
      @Component
      public class BeanForRequest {
      }
      
    • BeanForSession

      package com.colin.a08;
      
      import org.springframework.context.annotation.Scope;
      import org.springframework.stereotype.Component;
      
      /**
       * 作用域在session
       */
      @Scope("session")
      @Component
      public class BeanForSession {
      }
      
      
    • BeanForApplicaton

      package com.colin.a08;
      
      import org.springframework.context.annotation.Scope;
      import org.springframework.stereotype.Component;
      
      /**
       * 作用域在application
       */
      @Scope("application")
      @Component
      public class BeanForApplication {
      }
      
      
    • MyController

      这里注意:

      BeanForRequest与BeanForSession要加@Lazy注解,不然会出错,原因后续分析

      package com.colin.a08;
      
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Lazy;
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.ResponseBody;
      
      @Controller
      public class MyController {
      
          @Lazy
          @Autowired
          private BeanForRequest beanForRequest;
      
          @Lazy
          @Autowired
          private BeanForSession beanForSession;
          
          @Autowired
          private BeanForApplication application;
      
          @GetMapping(value = "/test", produces = "text/html")
          @ResponseBody
          public String test() {
              /**
               * 这里调用了的beanForRequest的tostring 是调用了代理的tostring
               * 如果没有,就会反射调用object的tostring方法 java > 9 会报非法访问异常 (IllegalAccessException)
               */
              String sb = "<ul>" +
                      "<li>" + "request scope:" + beanForRequest + "</li>" +
                      "<li>" + "session scope:" + beanForSession + "</li>" +
                      "<li>" + "application scope:" + application + "</li>" +
                      "</ul>";
              return sb;
          }
      }
      
      
    • 运行,浏览器访问http://localhost:8080/test, 发现后台报错

      image-20220606145439516

      • 报错信息为: 非法访问异常 (IllegalAccessException)
      • 这是因为,在jdk >= 9 的情况下 如果反射调用 jdk 中的一些方法 会报非法访问异常 (IllegalAccessException)
      • jdk <= 8 不会有问题
      • 解决方法1:可以在BeanForRequest中自己重写toString方法,这样反射调用时,就不会调用Object类的方法,就不会产生此问题
      • 解决方法2:可以添加虚拟机参数--add-opens java.base/java.lang=ALL-UNNAMED打开限制
    • 添加虚拟机参数--add-opens java.base/java.lang=ALL-UNNAMED后运行程序,再次访问http://localhost:8080/test

      image-20220606150024462

      • 多次刷新:发现每一次刷新BeanForRequest的值都会改变。因为 作用域在request域,每次请求都会创建一个新的bean。

        image-20220606150155146

      • 打开另一个浏览器访问http://localhost:8080/test,发现BeanForSession也会改变,作用域在session域,会在会话过程中存活,直到会话失效。

        image-20220606150457765

      • 无论开多少个浏览器BeanForApplication的值都不会改变,作用域在application域,会与应用共存活,直到应用程序停止才会失效。

        image-20220606150729380

②、演示2 - singleton 注入其它 scope 失效问题
  • 准备工作:准备单例对象E,注入多例对象F

    • E类

      package com.colin.a08.sub;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;
      
      @Slf4j
      @Component
      public class E {
          private F f;
      
          public E() {
              log.info("E() 构造方法");
          }
      
          @Autowired
          public void setF(F f) {
              this.f = f;
              log.info("setF(F f) {}", f.getClass());
          }
      
          public F getF() {
              return f;
          }
      }
      
      
    • F类

      package com.colin.a08.sub;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.context.annotation.Scope;
      import org.springframework.stereotype.Component;
      
      @Slf4j
      @Scope("prototype")
      @Component
      public class F {
      
          public F() {
              log.info("F() 构造方法");
          }
      }
      
      
    • 新建测试类A08_1进行测试

      package com.colin.a08;
      
      import com.colin.a08.sub.E;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      import org.springframework.context.annotation.ComponentScan;
      
      @Slf4j
      @ComponentScan("com.colin.a08.sub")
      public class A08_1 {
      
          public static void main(String[] args) {
              AnnotationConfigApplicationContext context =
                      new AnnotationConfigApplicationContext(A08_1.class);
      
              E e = context.getBean(E.class);
              log.info("{}", e.getF());
              log.info("{}", e.getF());
              log.info("{}", e.getF());
          }
      }
      
    • 测试结果:

      image-20220606151717187

      • 结果分析,我们发现虽然F是多例的,但是我们每次获取F都是同一个对象,而不是期望的多例对象。
③、singleton 注入其它 scope 失效问题分析
  • 对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
e 创建
e set 注入 f
f 创建
④、singleton 注入其它 scope 失效问题解决方案
  • 使用 @Lazy 生成代理,依赖注入注入代理对象

    • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 F 对象。

      使用f方法
      使用f方法
      使用f方法
      e 创建
      e set 注入 f代理
      f 创建
      f 创建
      f 创建
      • 创建多例对象F2,在E中依赖注入F2,但是与注入F不同,加上@Lasy

        F2类

        package com.colin.a08.sub;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.context.annotation.Scope;
        import org.springframework.stereotype.Component;
        
        @Slf4j
        @Scope("prototype")
        @Component
        public class F2 {
        
            public F2() {
                log.info("F2() 构造方法");
            }
        }
        
        

        E类

        @Slf4j
        @Component
        public class E {
            private F2 f2;
        .....
            @Lazy
            @Autowired
            public void setF2(F2 f2) {
                this.f2 = f2;
                log.info("setF2(F2 f2) {}", f2.getClass());
            }
            public F2 getF2() {
                return f2;
            }
        }
        

        在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,因为代码会调用到jdk 的类的方法

        @Slf4j
        @ComponentScan("com.colin.a08.sub")
        public class A08_1 {
        
            public static void main(String[] args) {
                AnnotationConfigApplicationContext context =
                        new AnnotationConfigApplicationContext(A08_1.class);
        
                E e = context.getBean(E.class);
                log.info("{}", e.getF());
                log.info("{}", e.getF());
                log.info("{}", e.getF());
                log.info("=====================================>>>>");
                log.info("{}", e.getF2());
                log.info("{}", e.getF2());
                log.info("{}", e.getF2());
            }
        }
        
        

        结果:可以看到加了@Lazy注解的F2每次获取都是一个新对象。

        image-20220606162322979

        注意:

        • @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
        • @Autowired 加在 set 方法的目的类似
  • 使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS),定义作用域 的时候指明代理模式

    • 创建多例对象F3,F3指明作用域时,指明代理模式为ScopedProxyMode.TARGET_CLASS(模式说明:ScopedProxyMode.TARGET_CLASS 创建基于类的代理(使用 CGLIB))

      F3类:

      package com.colin.a08.sub;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.context.annotation.Scope;
      import org.springframework.context.annotation.ScopedProxyMode;
      import org.springframework.stereotype.Component;
      
      @Slf4j
      @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
      @Component
      public class F3 {
      
          public F3() {
              log.info("F3() 构造方法");
          }
      }
      
      

      E类,注入F3

      package com.colin.a08.sub;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Lazy;
      import org.springframework.stereotype.Component;
      
      @Slf4j
      @Component
      public class E {
          ......
          private F3 f3;
      
          ......
      
          @Autowired
          public void setF3(F3 f3) {
              this.f3 = f3;
              log.info("setF3(F3 f3) {}", f3.getClass());
          }
          public F3 getF3() {
              return f3;
          }
      }
      
      

      在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,因为代码会调用到jdk 的类的方法

      /*
          如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
       */
      @Slf4j
      @ComponentScan("com.colin.a08.sub")
      public class A08_1 {
      
          public static void main(String[] args) {
              AnnotationConfigApplicationContext context =
                      new AnnotationConfigApplicationContext(A08_1.class);
      
              E e = context.getBean(E.class);
           ......
              log.info("=====================================>>>>");
              log.info("{}", e.getF3());
              log.info("{}", e.getF3());
              log.info("{}", e.getF3());
          }
      }
      

      结果:可以看到F3每次获取都是一个新对象

      image-20220606163457204

  • 使用ObjectFactory对象工厂,E想使用多例对象,不直接注入多例对象,而是注入对象的工厂。

    • 创建多例对象F4,E对象注入生产F4的工厂对象

      • F4类

        package com.colin.a08.sub;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.context.annotation.Scope;
        import org.springframework.stereotype.Component;
        
        @Slf4j
        @Scope(value = "prototype")
        @Component
        public class F4 {
        
            public F4() {
                log.info("F4() 构造方法");
            }
        }
        
      • E类

        package com.colin.a08.sub;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.ObjectFactory;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;
        
        @Slf4j
        @Component
        public class E {
           
        ......
            @Autowired
            private ObjectFactory<F4> f4;
        
        ......
            public F4 getF4() {
                return f4.getObject();
            }
        
        }
        
        
      • 在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,因为代码会调用到jdk 的类的方法

        /*
            如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
         */
        @Slf4j
        @ComponentScan("com.colin.a08.sub")
        public class A08_1 {
        
            public static void main(String[] args) {
                AnnotationConfigApplicationContext context =
                        new AnnotationConfigApplicationContext(A08_1.class);
        
                E e = context.getBean(E.class);
             ......
                log.info("=====================================>>>>");
                log.info("{}", e.getF4());
                log.info("{}", e.getF4());
                log.info("{}", e.getF4());
            }
        }
        
      • 结果:可以看到F4每次获取都是一个新对象

        image-20220606164158386

  • 注入ApplicationContext容器,简介获取对象

    • 创建多例对象F5,E对象注入ApplicationContext容器

      • F5类

        package com.colin.a08.sub;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.context.annotation.Scope;
        import org.springframework.stereotype.Component;
        
        @Slf4j
        @Scope(value = "prototype")
        @Component
        public class F5 {
        
            public F5() {
                log.info("F5() 构造方法");
            }
        }
        
        
      • E类

        package com.colin.a08.sub;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.ObjectFactory;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.annotation.Lazy;
        import org.springframework.stereotype.Component;
        
        @Slf4j
        @Component
        public class E {
        ......
            @Autowired
            private ApplicationContext context;
        
        ......
            public F5 getF5() {
                return context.getBean(F5.class);
            }
        
        }
        
      • 在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,因为代码会调用到jdk 的类的方法

        /*
            如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
         */
        @Slf4j
        @ComponentScan("com.colin.a08.sub")
        public class A08_1 {
        
            public static void main(String[] args) {
                AnnotationConfigApplicationContext context =
                        new AnnotationConfigApplicationContext(A08_1.class);
        
                E e = context.getBean(E.class);
             ......
                log.info("=====================================>>>>");
                log.info("{}", e.getF5());
                log.info("{}", e.getF5());
                log.info("{}", e.getF5());
            }
        }
        
      • 结果:可以看到F5每次获取都是一个新对象

        image-20220606164619208

⑤、总结
  1. 单例注入其它 scope 的四种解决方法
    • @Lazy
    • @Scope(value = “prototype”, proxyMode = ScopedProxyMode.TARGET_CLASS)
    • ObjectFactory
    • ApplicationContext
  2. 解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取

、AOP

  • 总述

    AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能

    除此以外,aspectj 提供了两种另外的 AOP 底层实现:

    • 第一种是通过 ajc 编译器编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
    • 第二种是通过 agent加载目标类时,修改目标类的字节码,织入增强功能
    • 作为对比,之前学习的代理是运行时生成新的字节码

    简单比较的话:

    • aspectj 在编译和加载时,修改目标字节码,性能较高
    • aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
    • aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行

a09AOP 实现之 ajc 编译器

①、ajc 编译器的演示
  • 演示准备,新创建一个module,**注意!!!**sdk一定要选择jdk16及以下,因为目前的 aspectj-maven-plugin 1.14.0(ajc编译插件) 最高只支持到 java 16。

    • 工程目录结构

      image-20220606171530200

    • pom文件

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>2.5.5</version>
              <relativePath/> <!-- lookup parent from repository -->
          </parent>
          <groupId>com.colin</groupId>
          <artifactId>A09</artifactId>
          <version>1.0</version>
      
            <!-- jdk8 -->
          <properties>
              <maven.compiler.source>8</maven.compiler.source>
              <maven.compiler.target>8</maven.compiler.target>
          </properties>
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
      
      
              <!--aspectj相关依赖-->
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjweaver</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjrt</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
              </dependency>
          </dependencies>
      
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
                  <!--   aspectj编译插件  -->
                  <plugin>
                      <groupId>org.codehaus.mojo</groupId>
                      <artifactId>aspectj-maven-plugin</artifactId>
                      <version>1.14.0</version>
                      <configuration>
                          <complianceLevel>1.8</complianceLevel>
                          <source>8</source>
                          <target>8</target>
                          <showWeaveInfo>true</showWeaveInfo>
                          <verbose>true</verbose>
                          <Xlint>ignore</Xlint>
                          <encoding>UTF-8</encoding>
                      </configuration>
                      <executions>
                          <execution>
                              <goals>
                                  <!-- use this goal to weave all your main classes -->
                                  <goal>compile</goal>
                                  <!-- use this goal to weave all your test classes -->
                                  <goal>test-compile</goal>
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
              </plugins>
          </build>
      </project>
      
      
    • MyAspect切面类: 值得注意的是,这个切面并没有被spring管理,解析切面的工作是编译时,编译器去做的。

      package com.colin.a09;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      /**
       * @Author Colin
       * @Description 切面
       * @Date 2022/6/6 17:06
       */
      @Aspect // ⬅️注意此切面并未被 Spring 管理
      public class MyAspect {
          private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
      
          @Before("execution(* com.colin.a09.MyService.foo())")
          public void before() {
              log.info("before()");
          }
      
      }
      
      
    • MyService业务类

      package com.colin.a09;
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.stereotype.Service;
      
      @Service
      public class MyService {
          private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
      
          public static void foo() {
              log.info("foo()");
          }
      }
      
    • A09启动类,为啥省事,直接在启动类进行代码测试。

      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.ConfigurableApplicationContext;
      
      /**
       *  注意几点
       *     1. 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
       *     2. 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器
       */
      @SpringBootApplication
      public class A09 {
      
          private static final Logger log = LoggerFactory.getLogger(A09.class);
      
          public static void main(String[] args) {
              ConfigurableApplicationContext context = SpringApplication.run(A09.class, args);
              MyService service = context.getBean(MyService.class);
              log.info("service class: {}", service.getClass());
              MyService.foo();
          }
      }
      
    • 运行程序: 发现目标被增强了,并且目标类并不是代理,而是目标类本身。如果运行没被增强,需要自己手动执行一下maven的编译(compile),让ajc编译器能够发生作用。

      image-20220606172458421

    • 我们通过idea看一下生成的MyService类的字节码(通过idea的反编译)

      image-20220606172818815

      • 我们看到了神奇的一幕:MyService的foo方法多了一步切面增强方法的调用。原来这就是增强的原因,相当于直接将增强的逻辑写在了原方法里,等同于改源代码。
②、总结
  1. 编译器也能修改 class 实现增强
  2. 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强

注意

  • 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
  • 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器

a10、 AOP 实现之 agent 类加载

  • Java Agent介绍
    • Java Agent 又叫做 Java 探针,是在 JDK1.5 引入的一种可以动态修改 Java 字节码的技术。
    • Java 类编译之后形成字节码被 JVM 执行,在 JVM 在执行这些字节码之前获取这些字节码信息,并且通过字节码转换器对这些字节码进行修改,来完成一些额外的功能。
    • 微服务技术链路追踪Skywalking就是基于探针。
①、agent的演示
  • 演示准备,新创建一个module,sdk选择jdk8

    • 项目代码与a09几乎一致,唯一不同的是pom不需要ajc的编译器插件,在resource下新建META-INF目录,目录下创建aop.xml,这是为了告诉探针程序切面与目标。

      image-20220607102442384

    • pom

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
          <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>2.5.5</version>
              <relativePath/> <!-- lookup parent from repository -->
          </parent>
          <groupId>com.colin</groupId>
          <artifactId>A10</artifactId>
          <version>1.0</version>
      
          <properties>
              <maven.compiler.source>8</maven.compiler.source>
              <maven.compiler.target>8</maven.compiler.target>
          </properties>
      
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter</artifactId>
              </dependency>
      
              <!-- aspectj 相关依赖-->
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjweaver</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjrt</artifactId>
              </dependency>
      
          </dependencies>
      </project>
      
      
    • MyAspect

      package com.colin.a10;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      @Aspect // ⬅️注意此切面并未被 Spring 管理
      public class MyAspect {
      
          private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
      
          @Before("execution(* com.colin.a10.MyService.*())")
          public void before() {
              log.info("before()");
          }
      }
      
      
    • MyService

      package com.colin.a10;
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.stereotype.Service;
      
      @Service
      public class MyService {
      
          private static final Logger log = LoggerFactory.getLogger(MyService.class);
      
          final public void foo() {
              log.info("foo()");
              this.bar();
          }
      
          public void bar() {
              log.info("bar()");
          }
      }
      
      
    • aop.xml

      <aspectj>
          <aspects>
              <aspect name="com.colin.a10.MyAspect"/>
              <weaver options="-verbose -showWeaveInfo">
                  <include within="com.colin.a10.MyService"/>
                  <include within="com.colin.a10.MyAspect"/>
              </weaver>
          </aspects>
      </aspectj>
      
    • A10

      package com.colin.a10;
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.ConfigurableApplicationContext;
      
      /*
          注意几点
          1. 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
          2. 运行时需要在 VM options 里加入 -javaagent:D:\\aspectjweaver-1.9.7.jar (指向jar包路径)
       */
      @SpringBootApplication
      public class A10 {
      
          private static final Logger log = LoggerFactory.getLogger(A10.class);
      
          public static void main(String[] args) {
              ConfigurableApplicationContext context = SpringApplication.run(A10.class, args);
              MyService service = context.getBean(MyService.class);
      
              // ⬇️MyService 并非代理, 但 foo 方法也被增强了, 做增强的 java agent, 在加载类时, 修改了 class 字节码
              log.info("service class: {}", service.getClass());
              service.foo();
             
              context.close();
      
          }
      }
      
      
  • 下载agent增强jar包: 下载地址:Maven Repository: org.aspectj » aspectjweaver » 1.9.7 (mvnrepository.com)

    image-20220607102937293

  • 开始测试,运行A10, 运行时需要在 VM options 里加入 -javaagent:D:\aspectjweaver-1.9.7.jar (指向jar包路径)。

    • 运行结果:

      image-20220607103105338

      • 可以看到MyService 的方法付都已经增强了,并且多了些红色字体的打印,说明探针(agent)发挥了作用。

      • 我们通过idea看一下生成的MyService类的字节码(通过idea的反编译), 发现字节码没有发生改变,这是因为增强功能是探针通过动态修改 Java 字节码实现的,是在类加载时实现的.

        image-20220607103326651

②、总结
    1. aop 的原理并非代理一种, agent 也能, 只要字节码变了, 行为就变了。

a11、AOP 实现之 proxy

①、演示1 - jdk 动态代理
  • 新建一个类JdkProxyDemo,使用JDK实现代理,从而增强目标方法。

    package com.colin.a11;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    @Slf4j
    public class JdkProxyDemo {
    
        /**
         * 定义一个接口,
         * JDK 的动态代理,要求目标类(被增强类)实现一个接口
         */
        interface Foo {
            void foo();
        }
    
    
        /**
         * 声明一个目标类
         */
        static final class Target implements Foo {
    
            @Override
            public void foo() {
                log.info("target foo");
            }
        }
    
    
        public static void main(String[] args) {
            /**
             * jdk 只能针对接口增强
             */
            //1、创建目标对象
            Target target = new Target();
    
            //2、使用jdk 提供的类Proxy,调用Proxy的newProxyInstance创建代理对象
            /**
             * newProxyInstance三个参数
             *    classLoader: 类加载器,
             *        因为生成代理是动态生成代理类的字节码(没有源代码编译阶段),需要有类加载器加载动态生成的字节码
             *    new Class[]{Foo.class}: 被代理类实现的接口
             *
             *    InvocationHandler : 第三个参数是一个接口,是需要我们编写的增强的逻辑处理。
             *                invoke方法: proxy 代理类本身
             *                            method 被代理类正在执行的方法对象
             *                            args   被代理类正在执行的方法参数
             *
             */
            ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
            Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            				log.info("before");
                            // 目标.方法(参数)
                            // 方法.invoke(目标, 参数); 反射调用目标方法
                            Object result = method.invoke(target, args);
                            log.info("after....");
                            return result; // 让代理也返回目标方法执行的结果
                        }
                    }
            );
    
            //打印代理类 类型
            log.info(proxy.getClass().toString());
            //执行代理对象方法
            proxy.foo();
    
        }
    }
    
  • 代码解析:

    • 首先定义一个接口Foo,这是因为JDK 的动态代理,要求目标类(被增强类)实现一个接口
    • 声明一个目标类Target实现Foo接口,这个类被作为被代理类(即被增强类)
    • Main创建目标对象,并根据目标类生成代理类,生成步骤
      • 使用jdk 提供的类Proxy,调用Proxy的newProxyInstance创建代理。
        • newProxyInstance方法参数一:classLoader: 类加载器, 因为生成代理是动态生成代理类的字节码(没有源代码编译阶段),需要有类加载器加载动态生成的字节码
        • newProxyInstance方法参数二:new Class[]{Foo.class}: 被代理类实现的接口
        • newProxyInstance方法参数三:InvocationHandler 第三个参数是一个接口,是需要我们编写的增强的逻辑处理。
      • InvocationHandler 接口的 invoke方法:
        • 参数一: proxy 代理类本身
        • 参数二: method 被代理类正在执行的方法对象
        • 参数三: args 被代理类正在执行的方法参数
    • 在InvocationHandler 接口的 invoke方法编写增强逻辑,并使用反射调用目标对象的方法。
    • 执行代理对象方法。
  • 执行结果:

    image-20220607112519387

⑴、总结
  • jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系。
  • 生成代理是动态生成代理类的字节码(没有源代码编译阶段)
②、演示2 - cglib 代理
  • 新建一个类CglibProxyDemo,使用cglib 实现代理,从而增强目标方法。

    package com.colin.a11;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * @Author Colin
     * @Description Cglib生成代理
     * @Date 2022/6/7 11:27
     */
    @Slf4j
    public class CglibProxyDemo {
    
        /**
         * 声明一个目标类
         */
        static class Target {
    
            public void foo() {
                log.info("target foo");
            }
        }
    
        public static void main(String[] args) {
            /**
             * 代理是子类型, 目标是父类型
             */
            //1、创建目标对象
            Target target = new Target();
    
            //2、使用cglib 提供的类Enhancer,调用Enhancer的create创建代理对象
            /**
             *  参数1:父类类型(生成的代理类是目标类的子类型)
             *  参数2:Callback接口 是方法增强逻辑接口,等同于jdk代理中的InvocationHandler接口
             *        但我们一般用它的子类型接口MethodInterceptor
             */
            /**
             *
             * @param o 代理对象本身
             * @param method 被代理类执行的方法对象
             * @param objects 被代理类执行的方法参数
             * @param methodProxy 被代理类执行的方法类的代理类对象
             * @return
             * @throws Throwable
             */
            /**
             * 调用目标方法: cglib提供了多种方式
             */
            // 方式一:用方法反射调用目标 需要使用目标对象
            // 方式二:使用 methodProxy 的invoke 方法,需要使用目标对象 但它可以避免反射调用
            // Object result = methodProxy.invoke(target, args); // 内部没有用反射, 需要目标 (spring 默认实现方法)
            // 方式三:使用 methodProxy 的invokeSuper 方法 不需要使用目标对象(需要代理对象) 也可以避免反射调用
            //Object result = methodProxy.invokeSuper(o, args); // 内部没有用反射, 需要代理
            Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {
                /**
                 *
                 * @param o 代理对象本身
                 * @param method 被代理类执行的方法对象
                 * @param objects 被代理类执行的方法参数
                 * @param methodProxy 被代理类执行的方法类的代理类对象
                 * @return
                 * @throws Throwable
                 */
    
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    log.info("before...");
                    /**
                     * 调用目标方法: cglib提供了多种方式
                     */
    
    
                    // 方式一:用方法反射调用目标 需要使用目标对象
                    Object result = method.invoke(target, args);
                    // 方式二:使用 methodProxy 的invoke 方法,需要使用目标对象 但它可以避免反射调用
                    // Object result = methodProxy.invoke(target, args); // 内部没有用反射, 需要目标 (spring 默认实现方法)
                    // 方式三:使用 methodProxy 的invokeSuper 方法 不需要使用目标对象(需要代理对象) 也可以避免反射调用
                    //Object result = methodProxy.invokeSuper(o, args); // 内部没有用反射, 需要代理
                    log.info("after...");
                    return result;
                }
            });
    
            //打印代理类对象类型
            log.info(proxy.getClass().toString());
    
            //使用代理类对象调用方法
            proxy.foo();
        }
    }
    
    
  • 代码解析

    • 声明一个目标类Target,这个类被作为被代理类(即被增强类),注意的是这个类不能被final修饰,因为cglib 动态代理是通过继承,让代理类成为目标类的子类,从而通过重写方法的方式增强目标类,如果目标类被final修饰,即不允许有子类,就无法生成代理。
    • 使用cglib 提供的类Enhancer,调用Enhancer的create创建代理对象
      • create方法参数解析:
        • 参数1:父类类型(生成的代理类是目标类的子类型)
        • 参数2:Callback接口 是方法增强逻辑接口,等同于jdk代理中的InvocationHandler接口但我们一般用它的子类型接口MethodInterceptor
        • MethodInterceptor接口intercept方法参数解析 (根据代码对比)
          • o 代理对象本身
          • method 被代理类执行的方法对象
          • objects 被代理类执行的方法参数
          • methodProxy 被代理类执行的方法类的代理类对象
    • MethodInterceptor接口intercept方法实现增强逻辑,并调用目标方法。
      • 调用目标方法: cglib提供了多种方式
        • 方式一:用方法反射调用目标 需要使用目标对象 : 如代码78行 Object result = method.invoke(target, args);这里的调用需要目标对象。
        • 方式二:使用 methodProxy 的invoke 方法,如代码80行 Object result = methodProxy.invoke(target, args); 需要使用目标对象 但它可以避免反射调用,效率更高。
        • 方式三:使用 methodProxy 的invokeSuper 方法 ,如代码82行 Object result = methodProxy.invokeSuper(o, args);不需要使用目标对象(需要代理对象) ,也可以避免反射调用,效率更高。
  • 运行结果:

    image-20220607122852155

⑴、总结
  • cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
  • 限制⛔:根据上述分析 final 类无法被 cglib 增强
  • 调用目标方法: cglib提供了多种方式,即可以反射调用,又可以避免反射调用,效率更高。

a12、JDK 动态代理进阶(探究JDK动态代理原理)

  • 我们知道,我们增强目标类,是为其生成了代理类,我们可不可以看一下生成的代理类源吗?

    • 很遗憾,我们看不到它的源代码,这是因为代理类的生成是动态的,JDK直接生成的是代理类的字节码,在内存中直接进行字节码的类加载。
  • 我们换个思路,模拟一下手动试着编写一下代理类的源代码。

①、演示1 - 模拟 jdk 动态代理
  • 原始版本:

    • 我们新建一个接口Foo,定义两个方法,一个有返回值,一个没有返回值。因为JDK代理需要有接口。

      package com.colin.a12.base;
      /**
       *接口
       */
      public interface Foo {
      
          void foo();
          int bar();
      }
      
    • 新建目标类Target,实现Foo接口。

      package com.colin.a12.base;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * 待增强的目标类
       */
      @Slf4j
      public class Target implements Foo {
      
          @Override
          public void foo() {
              log.info("Target foo()");
          }
      
          @Override
          public int bar() {
              log.info("Target bar()");
              return 0;
          }
      }
      
      
    • 创建代理类$Proxy0,模仿JDK代理类的命名。我们不知道怎么写,但我们知道代理增强无非是原有方法的增强。于是我们写下来以下代码

      package com.colin.a12.base;
      
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * 模拟编写 的代理类
       */
      @Slf4j
      public class $Proxy0 implements Foo{
      
      
          @Override
          public void foo() {
              log.info("$Proxy0 增强 before。。。。");
              //调用目标
              new Target().foo();
          }
      
          @Override
          public int bar() {
              return 0;
          }
      }
      
      
    • 编写测试类A12,调用代理类。

      package com.colin.a12.base;
      
      public class A12 {
      
          public static void main(String[] args) {
              //测试代理类
              $Proxy0 proxy = new $Proxy0();
              proxy.foo();
          }
      }
      
    • 结果:结果确实增强了,代码很简单,我们以后慢慢去优化它。把它慢慢变成JDK代理类的样子。

      image-20220607140145055

  • 改进版本1 将增强逻辑抽离。

    • 经历了原始版本,我们发现一个问题,就是我们的增强逻辑是耦合在代码里的,但是我们知道增强逻辑是不固定的,

      比如:有的增强是对数据库事务增强的,有的是权限的。。。。

      解决方法我们定义一个接口,作为代理类的成员变量,将增强逻辑通过接口传递给方法。

    • 创建接口InvocationHandler

      package com.colin.a12.improve1;
      
      /**
       * 增强逻辑接口
       */
      public interface InvocationHandler {
      
          void invoke();
      }
      
    • 修改代理类的逻辑,如下,将增强逻辑变成接口调用。

      package com.colin.a12.improve1;
      
      
      /**
       * 模拟编写 的代理类
       */
      public class $Proxy0 implements Foo {
      
          private InvocationHandler invocationHandler;
      
          public $Proxy0(InvocationHandler invocationHandler) {
              this.invocationHandler = invocationHandler;
          }
      
          @Override
          public void foo() {
              invocationHandler.invoke();
          }
      
          @Override
          public int bar() {
              invocationHandler.invoke();
              return 0;
          }
      }
      
      
    • 测试

      package com.colin.a12.improve1;
      
      
      import com.colin.a12.base.Target;
      import lombok.extern.slf4j.Slf4j;
      
      @Slf4j
      public class A12 {
      
          public static void main(String[] args) {
              //测试代理类
              $Proxy0 proxy = new $Proxy0(() -> {
                  log.info("事务增强");
                  new Target().foo();
              });
      
              $Proxy0 proxy1 = new $Proxy0(() -> {
                  log.info("权限增强");
                  new Target().foo();
              });
      
      
              proxy.foo();
              proxy1.foo();
          }
      }
      
    • 结果:确实可以实现不同增强逻辑的增强。

      image-20220607141518422

    • 出现的问题:我们使用代理类调用bar方法。发现我们不管调用的是代理类的什么方法都调用的是目标类的foo方法。

      package com.colin.a12.improve1;
      
      
      import com.colin.a12.base.Target;
      import lombok.extern.slf4j.Slf4j;
      
      @Slf4j
      public class A12 {
      
          public static void main(String[] args) {
         ......
              /**
               * 出现的问题
               * 不管调用的是代理类的什么方法都调用的是目标类的foo方法。
               */
              //调用bar方法
              log.info("=============================>>");
              proxy.bar();
              proxy1.bar();
          }
      }
      
      
    • 结果:

      image-20220607142012430

  • 改进版本2 处理改进版本1出现的问题

    • 分析原因: 改进版本1出现问题,不管调用的是代理类的什么方法都调用的是目标类的foo方法。

      • 代理类中方法重写逻辑中,只是接口的调用,并不能感知用户现在调用的是那个方法。
      • 代理类的接口内的逻辑,是通过构造传递的,对于同一个代理对象来说,接口的逻辑中调用目标的逻辑就定死了(无论调用代理类的那个方法都不会改变)
    • 修改InvocationHandler 接口,我们把调用方法与参数也通过接口传递给代理类

      package com.colin.a12.improve2;
      
      import java.lang.reflect.Method;
      
      /**
       * 增强逻辑接口
       */
      public interface InvocationHandler {
      
          /**
           * @param method 当前调用的方法
           * @param args   方法可能有参数,所以用一个Object数组,提高通用性
           */
          void invoke(Method method, Object[] args) throws Throwable;
      }
      
      
    • 修改代理类代码,给接口传递方法对象

      package com.colin.a12.improve2;
      
      
      import java.lang.reflect.Method;
      
      /**
       * 模拟编写 的代理类
       */
      public class $Proxy0 implements Foo {
      
          private InvocationHandler invocationHandler;
      
          public $Proxy0(InvocationHandler invocationHandler) {
              this.invocationHandler = invocationHandler;
          }
      
          @Override
          public void foo() {
              /**
               * 修改逻辑 传递当前调用方法
               */
              try {
                  Method foo = Foo.class.getMethod("foo");
                  invocationHandler.invoke(foo, new Object[0]);
              } catch (Throwable throwable) {
                  throwable.printStackTrace();
              }
          }
      
          @Override
          public int bar() {
              try {
                  Method bar = Foo.class.getMethod("bar");
                  invocationHandler.invoke(bar, new Object[0]);
              } catch (Throwable throwable) {
                  throwable.printStackTrace();
              }
              return 0;
          }
      }
      
      
    • 测试

      package com.colin.a12.improve2;
      
      
      import lombok.extern.slf4j.Slf4j;
      
      import java.lang.reflect.Method;
      
      @Slf4j
      public class A12 {
      
          public static void main(String[] args) {
      
              //目标类
              Target target = new Target();
      
              //测试代理类
              $Proxy0 proxy = new $Proxy0(new InvocationHandler() {
                  @Override
                  public void invoke(Method method, Object[] args) throws Throwable {
                      //增强逻辑
                      log.info("前置增强。。。。。。。。。。。。。");
                      //调用的方法对象已经有啦,通过反射调用方法
                      method.invoke(target, args);
                  }
              });
      
      
              proxy.foo();
              proxy.bar();
      
          }
      }
      
      
    • 测试 : 测试结果达到了我们的预期但还是有很大的改进空间。

      image-20220607143802341

  • 改进版本3 我们解决了改进版本1的问题,但还是和JDK生成的代理类有很大差别。

    • JDK对异常的处理很规范,还支持返回值,我们整体规范一下代码,异常处理、返回值支持。

    • 改进版本2中代理类,每次方法调用就会创建一个新的方法对象,这无疑也是一种浪费,优化一下。

    • 修改InvocationHandler 接口增加对返回值的支持

      import java.lang.reflect.Method;
      
      /**
       * 增强逻辑接口
       */
      public interface InvocationHandler {
      
          /**
           * @param method 当前调用的方法
           * @param args   方法可能有参数,所以用一个Object数组,提高通用性
           */
          Object invoke(Method method, Object[] args) throws Throwable;
      }
      
      
    • 修改代理类 优化代码

      • JDK代理类异常处理分两种情况
        • 一种是运行时异常,对于运行时异常JDK的做法是直接抛出
        • 一种是检查型异常,对于这种异常JDK会把它包装成运行时异常并抛出
      • 方法对象的优化
        • JDK代理类中会将方法对象声明成静态的方法对象,在静态代码块中,只初始化一次。
      package com.colin.a12.improve3;
      
      
      import java.lang.reflect.Method;
      import java.lang.reflect.UndeclaredThrowableException;
      
      /**
       * 模拟编写 的代理类
       */
      public class $Proxy0 implements Foo {
      
          private InvocationHandler invocationHandler;
      
          public $Proxy0(InvocationHandler invocationHandler) {
              this.invocationHandler = invocationHandler;
          }
      
          /**
           * 将方法对象声明成静态成员变量,防止重复创建
           */
          static Method foo;
          static Method bar;
      
          /**
           * 在静态代码块中初始化一次
           */
          static {
              try {
                  foo = Foo.class.getMethod("foo");
                  bar = Foo.class.getMethod("bar");
              } catch (NoSuchMethodException e) {
                  throw new NoSuchMethodError(e.getMessage());
              }
          }
      
          @Override
          public void foo() {
              /**
               * 修改逻辑 传递当前调用方法
               */
              try {
                  invocationHandler.invoke(foo, new Object[0]);
              } catch (RuntimeException | Error e) {
                  throw e;
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      
          @Override
          public int bar() {
              try {
                  Object result = invocationHandler.invoke(bar, new Object[0]);
                  return (int) result;
              } catch (RuntimeException | Error e) {
                  throw e;
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      }
      
      
    • 测试

      package com.colin.a12.improve3;
      
      
      import lombok.extern.slf4j.Slf4j;
      
      import java.lang.reflect.Method;
      
      @Slf4j
      public class A12 {
      
          public static void main(String[] args) {
      
              //目标类
              Target target = new Target();
      
              //测试代理类
              $Proxy0 proxy = new $Proxy0(new InvocationHandler() {
                  @Override
                  public Object invoke(Method method, Object[] args) throws Throwable {
                      //增强逻辑
                      log.info("前置增强。。。。。。。。。。。。。");
                      //调用的方法对象已经有啦,通过反射调用方法
                      return method.invoke(target, args);
                  }
              });
      
      
              proxy.foo();
              log.info(proxy.bar() + "");
      
          }
      }
      
      
    • 结果

      image-20220607145655711

  • 到这,我们自己编写的代理类已经和JDK,几乎一模一样了,只不过JDK的代理类会继承一个父类Proxy,Proxy父类中含有一个JDK自带的InvocationHandler接口。

    JDK自带的InvocationHandler接口会把代理对象本身传递过去。

    image-20220607145935909

  • 我们把InvocationHandler接口换成JDK自带的接口,再稍微改一下代理类的代码,继承Proxy父类。于是我们就得到了如下这个和JDK生成的代理类一模一样的类了

    package com.colin.a12.finalcode;
    
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    /**
     * 模拟编写 的代理类 最终版
     */
    public class $Proxy0 extends Proxy implements Foo {
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        /**
         * 将方法对象声明成静态成员变量,防止重复创建
         */
        static Method foo;
        static Method bar;
    
        /**
         * 在静态代码块中初始化一次
         */
        static {
            try {
                foo = Foo.class.getMethod("foo");
                bar = Foo.class.getMethod("bar");
            } catch (NoSuchMethodException e) {
                throw new NoSuchMethodError(e.getMessage());
            }
        }
    
        @Override
        public void foo() {
            /**
             * 修改逻辑 传递当前调用方法
             */
            try {
                h.invoke(this, foo, new Object[0]);
            } catch (RuntimeException | Error e) {
                throw e;
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    
        @Override
        public int bar() {
            try {
                Object result = h.invoke(this, bar, new Object[0]);
                return (int) result;
            } catch (RuntimeException | Error e) {
                throw e;
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    }
    
    
⑴、探究:JDK生成的代理类(看一看它的源码)
  • 我们无法看到JDK生成的代理类,但是我们就是好奇它长什么样子,我们可以通过一个强大的工具看到它,这个工具可以检测运行中的java程序,并可以反编译它们,它就是阿里巴巴出品Java诊断工具Arthas(阿尔萨斯)

    官网(Arthas 应用诊断利器 (aliyun.com))描述:

    当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

    1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
    2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
    3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
    4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
    5. 是否有一个全局视角来查看系统的运行状况?
    6. 有什么办法可以监控到JVM的实时运行状态?
    7. 怎么快速定位应用的热点,生成火焰图?
    8. 怎样直接从JVM内查找某个类的实例?
  • 准备工作: 利用a11的代码JdkProxyDemo ,因为Arthas要检测运行的java类,所以我们不能让JdkProxyDemo停止,我们在代码中加上一句控制台输入,让程序一直等到控制台输入,而不停止

    package com.colin.a11;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    @Slf4j
    public class JdkProxyDemo {
    
        .....
    
        public static void main(String[] args) throws IOException {
          ......
    
            //A12演示使用为了不让程序停止
            System.in.read();
    
        }
    
    }
    
    
  • 启动应用程序,并且启动Arthas(java -jar arthas-boot.jar )

    image-20220607152220988

  • 在cmd 窗口可以看到当前程序编号为9 ,所以我们输入编号9,介入应用程序。

    image-20220607152423568

  • 使用名称 jad 反编译运行中的类,命令: jad 类的全限定类名,这里我们输入jad com.colin.a11.$Proxy0

    我们就得到了JDK生成的代理类的源码。我把它粘贴出来

    image-20220607152609693

    package com.colin.a11;
    
    import com.colin.a11.JdkProxyDemo;
    import java.lang.invoke.MethodHandles;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    final class $Proxy0
    extends Proxy
    implements JdkProxyDemo.Foo {
        private static final Method m0;
        private static final Method m1;
        private static final Method m2;
        private static final Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            try {
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("com.colin.a11.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);
                return;
            }
            catch (NoSuchMethodException noSuchMethodException) {
                throw new NoSuchMethodError(noSuchMethodException.getMessage());
            }
            catch (ClassNotFoundException classNotFoundException) {
                throw new NoClassDefFoundError(classNotFoundException.getMessage());
            }
        }
    
        public final boolean equals(Object object) {
            try {
                return (Boolean)this.h.invoke(this, m1, new Object[]{object});
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString() {
            try {
                return (String)this.h.invoke(this, m2, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode() {
            try {
                return (Integer)this.h.invoke(this, m0, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void foo() {
            try {
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException {
            if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) {
                return MethodHandles.lookup();
            }
            throw new IllegalAccessException(lookup.toString());
        }
    }
    
    • JDK生成的代理类,还考虑了 Object中方法:hashCode,equals,toString
⑵、探究:JDK生成的代理类(动态生成代理类字节码的技术ASM)
  • 简介

    • ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
    • 其实大白话就是通过代码生成一个类的字节码,通过程序生成程序(太高端了),不过ASM的代码学习成本太高,因为涉及到字节码指令。自己写写不出来。
  • 虽然我们写不出来,但丝毫不影响我们对它的兴趣,我们可以换个思路。

    • 我们可以通过工具将演示1中我们模仿JDK写的代理类生成ASM代码 (idea中有插件,ASM Bytecode Outline)
    • 我们测试运行ASM代码得到字节码byte数组,把它输出到文件里 xxx.class。
    • 再通过idea,反编译这个xxx.class,看反编译的结果是不是和我们模仿JDK写的代理类一模一样。如果一样,说明ASM代码确实有如此神通。
  • 注意 ASM Bytecode Outline插件只支持JDK8 我们利用插件 把com.colin.a12.finalcode.$Proxy0 生成ASM代码 (右键选择 Show Bytecode outline)

    image-20220607154518571

  • 我们得到了ASM代码:

    image-20220607154716355

    package asm.com.colin.a12.finalcode;
    
    import java.util.*;
    
    import org.objectweb.asm.*;
    
    public class $Proxy0Dump implements Opcodes {
    
        public static byte[] dump() throws Exception {
    
            ClassWriter cw = new ClassWriter(0);
            FieldVisitor fv;
            MethodVisitor mv;
            AnnotationVisitor av0;
    
            cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/colin/a12/finalcode/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/colin/a12/finalcode/Foo"});
    
            cw.visitSource("$Proxy0.java", null);
    
            {
                fv = cw.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);
                fv.visitEnd();
            }
            {
                fv = cw.visitField(ACC_STATIC, "bar", "Ljava/lang/reflect/Method;", null, null);
                fv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
                mv.visitParameter("invocationHandler", 0);
                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitLineNumber(15, l0);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLineNumber(16, l1);
                mv.visitInsn(RETURN);
                Label l2 = new Label();
                mv.visitLabel(l2);
                mv.visitLocalVariable("this", "Lcom/colin/a12/finalcode/$Proxy0;", null, l0, l2, 0);
                mv.visitLocalVariable("invocationHandler", "Ljava/lang/reflect/InvocationHandler;", null, l0, l2, 1);
                mv.visitMaxs(2, 2);
                mv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
                mv.visitCode();
                Label l0 = new Label();
                Label l1 = new Label();
                Label l2 = new Label();
                mv.visitTryCatchBlock(l0, l1, l2, "java/lang/RuntimeException");
                mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Error");
                Label l3 = new Label();
                mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Throwable");
                mv.visitLabel(l0);
                mv.visitLineNumber(42, l0);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitFieldInsn(GETFIELD, "com/colin/a12/finalcode/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
                mv.visitVarInsn(ALOAD, 0);
                mv.visitFieldInsn(GETSTATIC, "com/colin/a12/finalcode/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
                mv.visitInsn(ICONST_0);
                mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
                mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
                mv.visitInsn(POP);
                mv.visitLabel(l1);
                mv.visitLineNumber(47, l1);
                Label l4 = new Label();
                mv.visitJumpInsn(GOTO, l4);
                mv.visitLabel(l2);
                mv.visitLineNumber(43, l2);
                mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
                mv.visitVarInsn(ASTORE, 1);
                Label l5 = new Label();
                mv.visitLabel(l5);
                mv.visitLineNumber(44, l5);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitInsn(ATHROW);
                mv.visitLabel(l3);
                mv.visitLineNumber(45, l3);
                mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
                mv.visitVarInsn(ASTORE, 1);
                Label l6 = new Label();
                mv.visitLabel(l6);
                mv.visitLineNumber(46, l6);
                mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
                mv.visitInsn(DUP);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
                mv.visitInsn(ATHROW);
                mv.visitLabel(l4);
                mv.visitLineNumber(48, l4);
                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
                mv.visitInsn(RETURN);
                Label l7 = new Label();
                mv.visitLabel(l7);
                mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l5, l3, 1);
                mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l6, l4, 1);
                mv.visitLocalVariable("this", "Lcom/colin/a12/finalcode/$Proxy0;", null, l0, l7, 0);
                mv.visitMaxs(4, 2);
                mv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_PUBLIC, "bar", "()I", null, null);
                mv.visitCode();
                Label l0 = new Label();
                Label l1 = new Label();
                Label l2 = new Label();
                mv.visitTryCatchBlock(l0, l1, l2, "java/lang/RuntimeException");
                mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Error");
                Label l3 = new Label();
                mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Throwable");
                mv.visitLabel(l0);
                mv.visitLineNumber(53, l0);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitFieldInsn(GETFIELD, "com/colin/a12/finalcode/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
                mv.visitVarInsn(ALOAD, 0);
                mv.visitFieldInsn(GETSTATIC, "com/colin/a12/finalcode/$Proxy0", "bar", "Ljava/lang/reflect/Method;");
                mv.visitInsn(ICONST_0);
                mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
                mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
                mv.visitVarInsn(ASTORE, 1);
                Label l4 = new Label();
                mv.visitLabel(l4);
                mv.visitLineNumber(54, l4);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
                mv.visitLabel(l1);
                mv.visitInsn(IRETURN);
                mv.visitLabel(l2);
                mv.visitLineNumber(55, l2);
                mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
                mv.visitVarInsn(ASTORE, 1);
                Label l5 = new Label();
                mv.visitLabel(l5);
                mv.visitLineNumber(56, l5);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitInsn(ATHROW);
                mv.visitLabel(l3);
                mv.visitLineNumber(57, l3);
                mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
                mv.visitVarInsn(ASTORE, 1);
                Label l6 = new Label();
                mv.visitLabel(l6);
                mv.visitLineNumber(58, l6);
                mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
                mv.visitInsn(DUP);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
                mv.visitInsn(ATHROW);
                Label l7 = new Label();
                mv.visitLabel(l7);
                mv.visitLocalVariable("result", "Ljava/lang/Object;", null, l4, l2, 1);
                mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l5, l3, 1);
                mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l6, l7, 1);
                mv.visitLocalVariable("this", "Lcom/colin/a12/finalcode/$Proxy0;", null, l0, l7, 0);
                mv.visitMaxs(4, 2);
                mv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
                mv.visitCode();
                Label l0 = new Label();
                Label l1 = new Label();
                Label l2 = new Label();
                mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NoSuchMethodException");
                mv.visitLabel(l0);
                mv.visitLineNumber(29, l0);
                mv.visitLdcInsn(Type.getType("Lcom/colin/a12/finalcode/Foo;"));
                mv.visitLdcInsn("foo");
                mv.visitInsn(ICONST_0);
                mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
                mv.visitFieldInsn(PUTSTATIC, "com/colin/a12/finalcode/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
                Label l3 = new Label();
                mv.visitLabel(l3);
                mv.visitLineNumber(30, l3);
                mv.visitLdcInsn(Type.getType("Lcom/colin/a12/finalcode/Foo;"));
                mv.visitLdcInsn("bar");
                mv.visitInsn(ICONST_0);
                mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
                mv.visitFieldInsn(PUTSTATIC, "com/colin/a12/finalcode/$Proxy0", "bar", "Ljava/lang/reflect/Method;");
                mv.visitLabel(l1);
                mv.visitLineNumber(33, l1);
                Label l4 = new Label();
                mv.visitJumpInsn(GOTO, l4);
                mv.visitLabel(l2);
                mv.visitLineNumber(31, l2);
                mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"});
                mv.visitVarInsn(ASTORE, 0);
                Label l5 = new Label();
                mv.visitLabel(l5);
                mv.visitLineNumber(32, l5);
                mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError");
                mv.visitInsn(DUP);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "getMessage", "()Ljava/lang/String;", false);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false);
                mv.visitInsn(ATHROW);
                mv.visitLabel(l4);
                mv.visitLineNumber(34, l4);
                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
                mv.visitInsn(RETURN);
                mv.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, l5, l4, 0);
                mv.visitMaxs(3, 1);
                mv.visitEnd();
            }
            cw.visitEnd();
    
            return cw.toByteArray();
        }
    }
    
    
  • 开始测试

    package com.colin.a12.testASM;
    
    import java.io.File;
    import java.io.FileOutputStream;
    
    public class Test {
    
        public static void main(String[] args) throws Exception {
            byte[] classByte = $Proxy0Dump.dump();
    
            FileOutputStream outputStream = new FileOutputStream(new File("D:\\1.class"));
            outputStream.write(classByte);
        }
    }
    
    
  • 进行反编译:发现跟我们编写的代码一模一样,不仅感叹 确实技术的魅力就在此处了。

    image-20220607155249777

  • 我们还可以直接加载这个字节码,不需要输出到文件

    package com.colin.a12.testASM;
    
    import com.colin.a12.finalcode.Foo;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    
    public class Test1 {
    
        public static void main(String[] args) throws Exception {
            byte[] classByte = $Proxy0Dump.dump();
    
            //直接使用自定义类加载器加载字节码
            ClassLoader loader = new ClassLoader() {
                @Override
                protected Class<?> findClass(String name) throws ClassNotFoundException {
                    return super.defineClass(name, classByte, 0, classByte.length);
                }
            };
    
            //加载类对象
            Class<?> proxyClass = loader.loadClass("com.colin.a12.finalcode.$Proxy0");
    
            //利用反射获取类的构造
            Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
    
            //利用反射创建实例
            Foo proxy = (Foo) constructor.newInstance(new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("before...");
                    System.out.println("调用目标");
                    return null;
                }
            });
    
            //调用
            proxy.foo();
        }
    }
    
    
    • 结果:

      image-20220607160002063

⑶、总结

代理一点都不难,无非就是利用了多态、反射的知识

  1. 方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部

  2. 通过接口回调将【增强逻辑】置于代理类之外

  3. 配合接口方法反射(是多态调用),就可以再联动调用目标方法

  4. 会用 arthas 的 jad 工具反编译代理类

  5. 限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现

②、演示2 - 方法反射优化
  • 方法的调用很耗性能,高版本的JDK对反射调用进行了优化。

  • JDK 方法反射调用,底层是使用MethodAccessor 的实现类。

    • 编写TestMethodInvoke类,测试方法调用,并利用反射查看调用的具体底层实现类

      package com.colin.a12.testMethodInvoke;
      
      
      import java.lang.reflect.Field;
      import java.lang.reflect.Method;
      
      /**
       * 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
       */
      public class TestMethodInvoke {
          public static void main(String[] args) throws Exception {
              Method foo = TestMethodInvoke.class.getMethod("foo", int.class);
              for (int i = 1; i <= 17; i++) {
                  show(i, foo);
                  foo.invoke(null, i);
              }
              System.in.read();
          }
      
      
          /**
           * JDK 方法反射调用,
           * 底层是使用MethodAccessor 的实现类
           */
          //通过反射查看内部细节
          private static void show(int i, Method foo) throws Exception {
              Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
              getMethodAccessor.setAccessible(true);
              Object invoke = getMethodAccessor.invoke(foo);
              if (invoke == null) {
                  System.out.println((i + ":" + null));
                  return;
              }
              Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
              delegate.setAccessible(true);
              System.out.println((i + ":" + delegate.get(invoke)));
          }
      
          public static void foo(int i) {
              System.out.println(i + ":" + "foo");
          }
      }
      
      
    • 运行结果:前16次都是真正的反射调用,但第17次调用反射方法时,调用的具体底层实现类变成了GeneratedMethodAccessor2

      image-20220607164926394

      • 我们通过Arthas看一下它的源码,可以清晰的看到方法调用变成了直接调用,从而提高了性能。

        image-20220607165252120

⑴、总结

  1. 前 16 次反射性能较低
  2. 第 17 次调用会生成代理类,优化为非反射调用
  3. 会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类

注意

运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED

a13、cglib 代理进阶

①、演示1 - 模拟 cglib 动态代理
  • 有了a12的铺垫,cglib 和 jdk 动态代理原理查不多

    • 回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
  • 调用目标时有所改进,增加了无反射调用目标的机制。

    • MethodInterceptor接口的intercept方法参数
  • 参数一 Object : 代理对象本身。

    • 参数二 Method: 被代理类执行的方法对象
  • 参数三 Objects : 被代理类执行的方法参数

    • 参数四 MethodProxy :被代理类执行的方法类的代理类对象(避免反射要用到,这里先不涉及)
  • 我们按照JDK代理的套路,写出cglib代理的基础版本(基础版本不涉及参数四 MethodProxy,以及避免反射逻辑)

    • Target 目标类

      package com.colin.a13.base;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * 目标类
       * cglib不需要目标类实现接口
       * cglib生成的代理类是目标类的子类
       */
      @Slf4j
      public class Target {
      
          public void save() {
              log.info("Target save()");
          }
      
          public void save(int i) {
              log.info("Target save(int)");
          }
      
          public void save(long j) {
              log.info("Target save(long)");
          }
      }
      
      
    • Proxy代理类 :基础版本 :MethodInterceptor接口的intercept方法参数的参数四 MethodProxy全部传递null。

      package com.colin.a13.base;
      
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.cglib.proxy.MethodInterceptor;
      
      import java.lang.reflect.Method;
      import java.lang.reflect.UndeclaredThrowableException;
      
      /**
       * 代理类  基础版本
       * 模拟cglib生成的代理类,
       * 代理类是目标类的子类
       */
      @Slf4j
      public class Proxy extends Target {
      
          private MethodInterceptor methodInterceptor;
      
          public Proxy(MethodInterceptor methodInterceptor) {
              this.methodInterceptor = methodInterceptor;
          }
      
          /**
           * 定义方法对象
           */
          static Method save0;
          static Method save1;
          static Method save2;
      
          static {
              try {
                  save0 = Target.class.getMethod("save");
                  save1 = Target.class.getMethod("save", int.class);
                  save2 = Target.class.getMethod("save", long.class);
              } catch (NoSuchMethodException e) {
                  throw new NoSuchMethodError(e.getMessage());
              }
          }
      
          @Override
          public void save() {
              try {
                  methodInterceptor.intercept(this, save0, new Object[0], null);
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      
          @Override
          public void save(int i) {
              try {
                  methodInterceptor.intercept(this, save1, new Object[]{i}, null);
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      
          @Override
          public void save(long j) {
              try {
                  methodInterceptor.intercept(this, save2, new Object[]{j}, null);
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      }
      
      
    • 测试

      package com.colin.a13.base;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.cglib.proxy.MethodInterceptor;
      import org.springframework.cglib.proxy.MethodProxy;
      
      import java.lang.reflect.Method;
      
      @Slf4j
      public class A13 {
      
      
          public static void main(String[] args) {
              /**
               * 测试基础版本
               */
              //1、目标对象
              Target target = new Target();
              //2、代理类对象
              Proxy proxy = new Proxy(new MethodInterceptor() {
                  @Override
                  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                      //前置增强
                      log.info("前置增强。。。。。。。。。。。。。");
                      return method.invoke(target, objects);
                  }
              });
              //3、调用代理
              proxy.save();
              proxy.save(1);
              proxy.save(2L);
          }
      }
      
      
    • 运行结果

      image-20220608104748621

②、MethodProxy
  • MethodProxy是方法的代理对象,有了它,我们就可以不利用反射就可以调用目标类的方法,原理后续研究。

  • 这里我们先来研究MethodProxy如何创建。

    • MethodProxy类中有静态方法create可以创建一个MethodProxy对象 :public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2)
      • 参数说明:
        • c1是目标类对象
        • c2是代理类对象
        • desc是要增强方法的参数返回值描述符(写法 偏底层,使用java字节码描述方法的描述符)
        • name1代理类中增强方法的方法名
        • name2代理类中未被增强方法名
  • create方法参数中第五个参数需要未被增强的原始方法名,所以我们要在代理类中,增加一组不增强的原始方法。

    • save方法为例

       //======================================不带增强逻辑的方法  原始方法=======================
          public void saveSuper() {
             super.save();
          }
          //======================================带增强逻辑的方法 =======================
          @Override
          public void save() {
              try {
                  methodInterceptor.intercept(this, save0, new Object[0], null);
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      
    • save方法为例创建MethodProxy对象 ()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void)

        static MethodProxy save0Proxy;
      
          static {
              try {
                  //()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void)
                  save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
              } catch (NoSuchMethodException e) {
                  throw new NoSuchMethodError(e.getMessage());
              }
          }
      
    • 我们将另外两个方法补齐,于是代理类被我们改造成了这样。

      • save0Proxy = MethodProxy.create(Target.class, Proxy.class, “()V”, “save”, “saveSuper”);
      • ()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void)
      • save1Proxy = MethodProxy.create(Target.class, Proxy.class, “(I)V”, “save”, “saveSuper”);
      • (I)V: (I)表示方法参数为int V:表示方法的返回值 无返回值(void)
      • save2Proxy = MethodProxy.create(Target.class, Proxy.class, “(J)V”, “save”, “saveSuper”);
        • (J)V: (J)表示方法参数为long V:表示方法的返回值 无返回值(void)
      package com.colin.a13.finalCode;
      
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.cglib.proxy.MethodInterceptor;
      import org.springframework.cglib.proxy.MethodProxy;
      
      import java.lang.reflect.Method;
      import java.lang.reflect.UndeclaredThrowableException;
      
      /**
       * 代理类  基础版本
       * 模拟cglib生成的代理类,
       * 代理类是目标类的子类
       */
      @Slf4j
      public class Proxy extends Target {
      
          private MethodInterceptor methodInterceptor;
      
          public Proxy(MethodInterceptor methodInterceptor) {
              this.methodInterceptor = methodInterceptor;
          }
      
          /**
           * 定义方法对象
           */
          static Method save0;
          static Method save1;
          static Method save2;
          static MethodProxy save0Proxy;
          static MethodProxy save1Proxy;
          static MethodProxy save2Proxy;
      
          static {
              try {
                  save0 = Target.class.getMethod("save");
                  save1 = Target.class.getMethod("save", int.class);
                  save2 = Target.class.getMethod("save", long.class);
                  //()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void)
                  save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
                  //(I)V: (I)表示方法参数为int V:表示方法的返回值 无返回值(void)
                  save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
                  //(J)V: (J)表示方法参数为long V:表示方法的返回值 无返回值(void)
                  save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
              } catch (NoSuchMethodException e) {
                  throw new NoSuchMethodError(e.getMessage());
              }
          }
      
      
          //======================================不带增强逻辑的方法  原始方法=======================
          public void saveSuper() {
              super.save();
          }
      
          public void saveSuper(int i) {
              super.save(i);
          }
      
          public void saveSuper(long j) {
              super.save(j);
          }
      
          //======================================带增强逻辑的方法 =======================
          @Override
          public void save() {
              try {
                  methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      
          @Override
          public void save(int i) {
              try {
                  methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      
          @Override
          public void save(long j) {
              try {
                  methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);
              } catch (Throwable e) {
                  throw new UndeclaredThrowableException(e);
              }
          }
      }
      
      
    • 运行测试

      package com.colin.a13.finalCode;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.cglib.proxy.MethodInterceptor;
      import org.springframework.cglib.proxy.MethodProxy;
      
      import java.lang.reflect.Method;
      
      @Slf4j
      public class A13 {
      
      
          public static void main(String[] args) {
              /**
               * 测试基础版本
               */
              //1、目标对象
              Target target = new Target();
              //2、代理类对象
              Proxy proxy = new Proxy(new MethodInterceptor() {
                  @Override
                  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                      //前置增强
                      log.info("前置增强。。。。。。。。。。。。。");
                      //这是反射调用
                      //return method.invoke(target, objects);
      
                      //使用methodProxy.invoke 避免反射调用  需要目标对象
                      //return methodProxy.invoke(target, objects);
      
                      //使用methodProxy.invokeSuper 避免反射调用  需要代理对象
                      return methodProxy.invokeSuper(o, objects);
                  }
              });
              //3、调用代理
              proxy.save();
              proxy.save(1);
              proxy.save(2L);
          }
      }
      
      
    • 效果和反射调用是一样的。
      image-20220608113221325

a14、cglib 避免反射调用

项目源码:https://gitee.com/ZhongZaiMaYun/spring-senior.git

spring的工具类:

  • 注解工具类AnnotationUtils

    //获取某个类的某个注解
    ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
    
  • 工厂类 CachingMetadataReaderFactory 可以获取类的元信息

    String path = "classpath*:com/colin/a05/component/bean1.class";
    Resource resource = context.getResource(path);
    
    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
    
    MetadataReader metadataReader = factory.getMetadataReader(resource);
    log.info("类名:{}", metadataReader.getClassMetadata().getClassName());
    log.info("注解信息(是否加了Component):{}", metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName()));
    log.info("注解信息(是否加了Component或者Component派生注解):{}", metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
    
    
  • 生成bean 的名字 使用spring提供的注解bean名字生成器AnnotationBeanNameGenerator

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
        .genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
        .getBeanDefinition();

AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
String beanName = generator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值