spring高级篇(一)

news2024/11/24 9:57:43

1、ApplicationContext与BeanFactory

        BeanFactory是ApplicationContext的父级接口:(citl+alt+u查看类关系图)

        在springboot的启动类中,我们通过SpringApplication.run方法拿到的是继承了ApplicationContext的ConfigurableApplicationContext接口:

 ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringPlusApplication.class, args);

        通过.getClass() 方法获取 Spring 应用上下文的类类型,得知在运行时,在本类中ApplicationContext 实现类的具体类型是AnnotationConfigServletWebServerApplicationContext

System.out.println(applicationContext.getClass());

        IOC,DI,管理SpringBean的生命周期,都是由beanFactory的实现类完成:

  //例:通过反射得到存放bean的数组
        Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        singletonObjects.setAccessible(true);
        //得到BeanFactory
        ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();

        //解析beanFactory中有多少单例的bean
        Map<String,Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);

        map.forEach((k,v)->{
            System.out.println(k+":"+v);
        });

        注册两个自定义的bean:

@Component
public class Component1 {

    private static final Logger log = LoggerFactory.getLogger(Component1.class);

    @Autowired
    private ApplicationEventPublisher publisher;

    public void register(){
        log.debug("用户注册");
        publisher.publishEvent(new UserRegisterEvent(this));
    }

}
@Component
public class Component2 {

    private static final Logger log = LoggerFactory.getLogger(Component2.class);

    @EventListener
    public void sendMessage(UserRegisterEvent event){
        log.debug(event.toString());
        log.info("发送短信");
    }
}
 //例:通过反射得到存放bean的数组
        Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        singletonObjects.setAccessible(true);
        //得到BeanFactory
        ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();

        //解析beanFactory中有多少单例的bean
        Map<String,Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);


        map.entrySet().stream().filter(k->k.getKey().startsWith("component")).forEach(e->{
            System.out.println(e.getKey()+":"+e.getValue());
        });

        在上面的案例中,还可以观察到一种现象,即自定义的两个类,Component1和Component2的类名首字母都是大写,而在解析beanFactory时,.startsWith("component") 方法的component却是小写,其原因在于:

Spring中默认情况下,Bean的名称是小写的,Bean的名称是在定义Bean时指定的,如果你没有显式地为Bean指定名称,Spring会根据一定的命名规则自动生成Bean的名称。在自动生成名称时,通常会将类名的首字母小写作为Bean的名称。

         同时ApplicationContext也对BeanFactory通过聚合的方式进行了功能的扩展

  • MessageSource:国际化翻译
  • ResourcePatternResolver:根据通配符获取资源
  • EnvironmentCapable:获取配置信息
  • ApplicationEventPublisher:发送事件
1.1、MessageSource

        在resource目录下准备四个文件:

 messages_en.properties:

hi=Hello

 messages_ja.properties:

hi=こんにちは

 messages_zh.properties:

hi=你好
System.out.println(applicationContext.getMessage("hi", null, Locale.CHINESE));
System.out.println(applicationContext.getMessage("hi", null, Locale.ENGLISH));
System.out.println(applicationContext.getMessage("hi", null, Locale.JAPANESE));

1.2、ResourcePatternResolver
Resource resource = applicationContext.getResource("classpath*:META-INF/spring.factories");
System.out.println(resource);

1.3、 EnvironmentCapable
System.out.println(applicationContext.getEnvironment().getProperty("java_home"));   

System.out.println(applicationContext.getEnvironment().getProperty("server.port"));

1.4、 ApplicationEventPublisher

        发布事件通知,类似于消息中间件的功能,可以实现解耦:

        首先创建一个中间类:

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

        消息生产者,调用publisher.publishEvent()

@Component
public class Component1 {

    private static final Logger log = LoggerFactory.getLogger(Component1.class);

    @Autowired
    private ApplicationEventPublisher publisher;

    public void register(){
        log.debug("用户注册");
        publisher.publishEvent(new UserRegisterEvent(this));
    }

}

        消息消费者,将UserRegisterEvent作为参数传入,同时方法上需要加上@EventListener

@Component
public class Component2 {

    private static final Logger log = LoggerFactory.getLogger(Component2.class);

    @EventListener
    public void sendMessage(UserRegisterEvent event) {
        log.debug(event.toString());
        log.info("发送短信");
    }
}

        主类:

Component1 component1 = applicationContext.getBean("component1", Component1.class);
component1.register();

        ApplicationEventPublisher的局限性:

不支持分布式事件发布。在典型的 Spring 应用程序中,ApplicationEventPublisher是用于在单个应用程序上下文中发布事件的,而不是用于跨应用程序或分布式系统的事件传播。所以在分布式系统中,推荐使用更加成熟的消息队列、事件总线或者分布式事件处理系统。

1.5、小结:

        ApplicationContext和BeanFactory的区别和联系:

        区别:

  • ApplicationContext:ApplicationContext是 BeanFactory的子接口之一。它提供了更多的企业级功能,如国际化、事件传播、资源加载等。ApplicationContext在启动时就会加载所有的单例 bean,并在初始化过程中完成依赖注入和各种后处理器的应用。ApplicationContext通常在实际开发中更为常用,因为它提供了更多的特性和功能。
  • BeanFactory:BeanFactory是 Spring 框架的核心接口之一,它提供了 bean 的配置、创建、管理和查找功能。BeanFactory的实现类负责加载 bean 的定义信息,并且在需要时才实例化 bean。与 ApplicationContext相比,BeanFactory更轻量级,因为它只在需要时才初始化 bean,节省了资源。但是它不提供像 ApplicationContext那样的扩展功能。

        联系:

  • 接口关系:ApplicationContext是 BeanFactory的子接口,因此 ApplicationContext包含了 BeanFactory的所有功能,并且在此基础上提供了更多的功能。
  • 功能:BeanFactory是 Spring 框架中负责管理 bean 的核心接口,而 ApplicationContext在 BeanFactory的基础上提供了更多的企业级功能和扩展。因此,ApplicationContext 在实际开发中更常用,但如果对资源有较高要求,可以考虑使用轻量级的 BeanFactory。

2、容器实现

        列举一些BeanFactory常见的实现:

  • XmlBeanFactoryXmlBeanFactory是 Spring 最基本的容器实现之一,它从 XML 文件中加载 bean 的定义信息,并在需要时实例化和管理 bean。这是 Spring 早期版本中最常用的容器实现之一,但现在已经不推荐使用,因为它在初始化时会加载所有的 bean,可能会造成性能问题。

  • ClassPathXmlApplicationContext: ClassPathXmlApplicationContext是基于 XML 配置文件的应用程序上下文实现。它从类路径中加载 XML 文件,并使用其中的 bean 定义来初始化应用程序上下文。这是使用 Spring 框架时最常见的容器实现之一。

  • FileSystemXmlApplicationContext: FileSystemXmlApplicationContext也是基于 XML 配置文件的应用程序上下文实现,但它从文件系统中加载 XML 文件。与 ClassPathXmlApplicationContext不同,它需要指定 XML 文件的绝对路径或相对路径。

  • AnnotationConfigApplicationContext: AnnotationConfigApplicationContext是基于注解的应用程序上下文实现。它会扫描指定的包路径,查找带有特定注解的类(如@Configuration、@Component等),并根据这些类中的配置来初始化 bean。

  • GenericApplicationContext: GenericApplicationContext是一个泛型的应用程序上下文实现,它可以通过编程方式动态地注册和管理 bean。与基于 XML 或注解的上下文实现不同,GenericApplicationContext允许在运行时动态地添加、修改和删除 bean。(是不带后处理器,较为干净的实现,后续演示后处理器使用该实现完成)

  • DefaultListableBeanFactory是 BeanFactory 最重要的实现,像控制反转依赖注入功能,都是它来实现

2.1、DefaultListableBeanFactory

        为了演示功能,首先创建几个静态内部类:

  •  Config类的作用是注册自定义bean,类上加入@Configuration  注解。
  •  bean3和bean4同时实现了Inter接口。
/**
     * 注册bean
     */
    @Configuration
    static class config {

        @Bean
        public Bean1 createBean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 createBean2() {
            return new Bean2();
        }

        @Bean
        public Bean3 createBean3() {
            return new Bean3();
        }

        @Bean
        public Bean4 createBean4() {
            return new Bean4();
        }
    }
    
    interface Inter{

    }
    

    static class Bean1 {

        private static final Logger log = LoggerFactory.getLogger(Bean1.class);

        @Autowired
        Bean2 bean2;

        //那么如果同时加了 @Autowired 和 @Resource 呢?答案是以 @Autowired为准。因为在后处理器中它的位置靠前
//        @Autowired//如果同一个接口有多个实现类,需要注入,必须要使用@Qualifier 或者 和bean的名字同名
        @Resource(name = "createBean4")//如果使用resource并且标注了name,按照name中的bean装配
        private Inter createBean3;

        public Bean1() {
            log.info("Bean1 created");
        }

        public Bean2 getBean2() {
            return bean2;
        }

        public Inter getInter() {
            return createBean3;
        }


    }


    static class Bean2 {
        private static final Logger log = LoggerFactory.getLogger(Bean2.class);

        public Bean2() {
            log.info("Bean2 created");
        }
    }

    static class Bean3 implements Inter{

        private static final Logger log = LoggerFactory.getLogger(Bean3.class);

        public Bean3(){
            log.info("Bean3 created");
        }
    }

    static class Bean4 implements Inter{
        private static final Logger log = LoggerFactory.getLogger(Bean4.class);

        public Bean4(){
            log.info("Bean4 created");
        }
    }

        主类:

//DefaultListableBeanFactory是beanFactory最重要的实现
 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//定义bean 包括范围,初始化,销毁 这里定义的是config bean
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(config.class).setScope("singleton").getBeanDefinition();
//注册config bean
beanFactory.registerBeanDefinition("config", beanDefinition);

        我们还需要给beanFactory添加一些常用的后处理器:(如果不添加后处理器,beanFactory不会主动解析@Configuration @Bean 等注解)

 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        可以遍历查看后处理器:

      for (String name : beanFactory.getBeanDefinitionNames()) {
            //org.springframework.context.annotation.internalConfigurationAnnotationProcessor 解析@Bean
            //org.springframework.context.annotation.internalAutowiredAnnotationProcessor 解析@AutoWired @Value
            //org.springframework.context.annotation.internalCommonAnnotationProcessor 解析@Resource
            //org.springframework.context.event.internalEventListenerProcessor
            //org.springframework.context.event.internalEventListenerFactory
            System.out.println(name);
        }

         此时只是bean工厂中有了这些后处理器中的bean,但是要生效还需要:

         其中beanFactory.getBeansOfType():根据类型获取多个bean

        processor.postProcessBeanFactory():执行bean工厂后处理器

Collection<BeanFactoryPostProcessor> values = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values();
for (BeanFactoryPostProcessor processor : values) {
   processor.postProcessBeanFactory(beanFactory);
}

//再次查看beanFactory中的bean
for (String name : beanFactory.getBeanDefinitionNames()) {
   System.out.println(name);
}

        此时beanFactory已经有了解析@Configuration @Bean 等注解的能力:

        因为此时bean1中有一个使用@AutoWired 注入的bean2,此时尝试一下获取bean2:

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

         可以发现bean2没有被初始化:

         需要添加bean的相关后处理器,解析@AutoWired @Value @Resource 等注解信息:

  • internalConfigurationAnnotationProcessor:解析@Bean
  • internalAutowiredAnnotationProcessor :解析@AutoWired @Value
  • internalCommonAnnotationProcessor: 解析@Resource

Collection<BeanPostProcessor> postProcessors = beanFactory.getBeansOfType(BeanPostProcessor.class).values();
for (BeanPostProcessor postProcessor : postProcessors) {
     System.out.println("<<<<<"+postProcessor);
     beanFactory.addBeanPostProcessor(postProcessor);
}

for (String name : beanFactory.getBeanDefinitionNames()) {
     System.out.println(name);
}
System.out.println(beanFactory.getBean(Bean1.class).getBean2());

        至此所有的bean都加载完毕。但是有一个问题:bean3和bean4都实现了Inter接口,我在bean1中对其注入时,如果类型是bean3和bean4的父类,那么使用@AutoWired注解将注入哪一个bean?

        如果使用@AutoWired注解,需要配合@Qualifier 注解确定具体注入哪一个实现类。


        如果使用@Resource(name = "createBean4")注解,按照name属性的bean进行装配:

        属性名虽然叫createBean3,但是实际注入的是@Resource 的name所在的bean:

        那如果同时标上了@AutoWired  @Resource 呢?答案是以 @Autowired为准。因为在后处理器中它的位置靠前:

        CommonAnnotationBeanPostProcessor的order排序:(解析@Resource

         AutowiredAnnotationProcessor 的order排序:(解析@AutoWired @Value

        order排序的数字越小,优先级越高。所以同时标注@AutoWired  @Resource 时,以@AutoWired  为准。

2.2、常见 ApplicationContext 实现

        首先自定义两个bean:

static class Bean1{

    
}


static class Bean2{

   private Bean1 bean1;

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

   public Bean1 getBean1() {
       return bean1;
   }

}

        使用xml的方式进行bean管理: 

<?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">

<!--        定义bean-->
    <bean id="bean1" class="com.itbaima.a01.ApplicaionContextDemo.Bean1"/>

    <bean id="bean2" class="com.itbaima.a01.ApplicaionContextDemo.Bean2">
        <property name="bean1" ref="bean1"/>
    </bean>
</beans>
 2.2.1、基于classPath 读取xml文件
    public static void testClassPathXmlApplicationContext(){
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("test1.xml");
        for (String name : applicationContext.getBeanDefinitionNames()) {
            System.out.println(name);
        }

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

2.2.2、基于磁盘路径读取xml文件
    public static void testFileSystemXmlApplicationContext(){
        FileSystemXmlApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\Idea_workspace\\2024\\spring-plus\\src\\main\\resources");
        for (String name : applicationContext.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println(applicationContext.getBean(Bean2.class).getBean1());
    }
2.2.3、基于java配置类实现

        会默认加上后处理器:

  @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;
        }
    }
    public static void testAnnotationConfigApplicationContext(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
        for (String name : applicationContext.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println(applicationContext.getBean(Bean2.class).getBean1());
    }

2.2.4、基于java配置类实现 web相关
 public static void testAnnotationConfigServletWebServerApplicationContext(){
        AnnotationConfigServletWebServerApplicationContext applicationContext = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
 @Configuration
    static class WebConfig{

        /**
         * 将内嵌tomcat注册成bean
         * @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")
        public Controller controller(){
            return ((httpServletRequest, httpServletResponse) -> {
                httpServletResponse.getWriter().write("hello");
                return null;
            });
        }
    }
2.3、小结
  • beanFactory 可以通过 registerBeanDefinition 注册一个 bean definition 对象

    • 我们平时使用的配置类、xml、组件扫描等方式都是生成 bean definition 对象注册到 beanFactory 当中

    • bean definition 描述了这个 bean 的创建蓝图:scope 是什么、用构造还是工厂创建、初始化销毁方法是什么,等等

  • beanFactory 需要手动调用 beanFactory 后处理器对它做增强

    • 例如通过解析 @Bean、@ComponentScan 等注解,来补充一些 bean definition

  • beanFactory 需要手动添加 bean 后处理器,以便对后续 bean 的创建过程提供增强

    • 例如 @Autowired,@Resource 等注解的解析都是 bean 后处理器完成的

    • bean 后处理的添加顺序会对解析结果有影响,见视频中同时加 @Autowired,@Resource 的例子

  • beanFactory 需要手动调用方法来初始化单例

  • beanFactory 需要额外设置才能解析 ${} 与 #{}

3、Bean的生命周期

        Spring 中的 bean 生命周期通常包括以下几个阶段:

  1. 实例化(Instantiation):容器根据配置信息(XML、注解等)创建 bean 的实例。这通常涉及到实例化目标 bean 类,并根据配置注入依赖

  2. 属性设置(Population):容器通过依赖注入(Dependency Injection)将配置的属性值或引用设置到 bean 实例中。这包括基本属性、引用类型、集合类型等。

  3. 初始化(Initialization)

    • Initialization callbacks:在 bean 实例化完成并且所有属性设置完毕后,容器会调用 bean 的InitializingBean接口的afterPropertiesSet()方法,或者调用在配置中声明的初始化方法。
    • BeanPostProcessors:在调用初始化方法之前和之后,容器会调用所有注册的 BeanPostProcessor实现类的postProcessBeforeInitialization() postProcessAfterInitialization()方法,允许对 bean 进行自定义的初始化处理。
  4. 使用(In Use):在初始化完成后,bean 可以被容器或其他 bean 使用。

  5. 销毁(Destruction)

    • DisposableBean:如果 bean 实现了 DisposableBean接口,容器在销毁 bean 之前会调用其destroy() 方法。
    • 自定义销毁方法:在配置中可以声明自定义的销毁方法,在容器关闭时调用。
  6. 销毁回调(Destruction callbacks):与初始化类似,容器在销毁 bean 之前会调用所有注册的 BeanPostProcessor实现类的 postProcessBeforeDestruction() 方法,允许对 bean 进行自定义的销毁处理。

@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

    private static final Logger log = LoggerFactory.getLogger(MyBeanPostProcessor.class);

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 销毁之前执行, 如 @PreDestroy");
        }
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 实例化之前执行, 这里返回的对象会替换掉原本的 bean");
        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 实例化之后执行, 这里如果返回 false 会跳过依赖注入阶段");
//            return false;
        }
        return true;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 依赖注入阶段执行, 如 @Autowired、@Value、@Resource");
        }
        return pvs;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 初始化之前执行, 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 初始化之后执行, 这里返回的对象会替换掉原本的 bean, 如代理增强");
        }
        return bean;
    }
}

4、常见的Bean后处理器

        首先创建几个bean:

public class Bean2 {
}
public class Bean3 {
}
public class Bean1 {

    private static final Logger log = LoggerFactory.getLogger(Bean1.class);
    Bean3 bean3;

    Bean2 bean2;

    @Autowired
    public void setBean1(Bean3 bean3) {
        log.debug("setBean3");
        this.bean3 = bean3;
    }

    @Resource
    public void setBean2(Bean2 bean2) {
        log.debug("setBean2");
        this.bean2 = bean2;
    }

    @Autowired
    public void autowiredMethod(@Value("${JAVA_HOME}")String name){
        log.debug("注入:"+name);
    }


    @PostConstruct
    public void initMethod(){
        log.debug("初始化");
    }

    @PreDestroy
    public void destroyMethod(){
        log.debug("销毁");
    }
}
@ConfigurationProperties(prefix = "java")
public class Bean4 {

    String home;

    String version;


    public Bean4() {
    }

    public Bean4(String home, String version) {
        this.home = home;
        this.version = version;
    }

    /**
     * 获取
     * @return home
     */
    public String getHome() {
        return home;
    }

    /**
     * 设置
     * @param home
     */
    public void setHome(String home) {
        this.home = home;
    }

    /**
     * 获取
     * @return version
     */
    public String getVersion() {
        return version;
    }

    /**
     * 设置
     * @param version
     */
    public void setVersion(String version) {
        this.version = version;
    }

    public String toString() {
        return "Bean4{home = " + home + ", version = " + version + "}";
    }
}

        在主类中,我们使用ApplicationContext的GenericApplicationContext实现(在容器实现中提到过,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();

//销毁容器
context.close();

        此时并没有打印出关于bean中定义的任何信息。

4.1、解析@AutoWired @Value

        如果需要解析bean1中的@AutoWired @Value ,则主类中需要增加:

//这行代码用于设置默认的 Bean 工厂的自动装配候选项解析器(AutowireCandidateResolver)。
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
//AutowiredAnnotationBeanPostProcessor 是 Spring 框架中用于处理 @Autowired 注解的后置处理器,它会在 bean 实例化后,对标记了 @Autowired 注解的字段进行自动注入。
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);

4.2、解析@Resource @PostConstruct @PreDestroy
context.registerBean(CommonAnnotationBeanPostProcessor.class);

4.3、解析@ConfigurationProperties
//将 ConfigurationPropertiesBindingPostProcessor 注册到 Spring 容器中的默认 Bean 工厂中。
//ConfigurationPropertiesBindingPostProcessor 是 Spring Boot 中用于处理 @ConfigurationProperties 注解的后置处理器。
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());


System.out.println(context.getBean("bean4"));
4.4、@Autowired bean 后处理器运行分析
 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
 beanFactory.registerSingleton("bean2", new Bean2());
 beanFactory.registerSingleton("bean3", new Bean3());
        
 //解析@value不出异常
       beanFactory.setAutowireCandidateResolver(newContextAnnotationAutowireCandidateResolver());
 //解析${}需要加上
 beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

        通过反射,演示一下AutowiredAnnotationBeanPostProcessor后处理器运行的过程:

AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();

//设置后处理器要处理的beanFactory,因为bean2,bean3都在beanFactory中
processor.setBeanFactory(beanFactory);

       AutowiredAnnotationBeanPostProcessor执行依赖注入,实际是调用了:

  processor.postProcessProperties(null, bean1, "bean1");

        在该方法内部,再次调用了

        因为findAutowiringMetadata() 是私有方法,所以通过反射的方式进行调用处理:

//通过反射获取findAutowiringMetadata方法
Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
findAutowiringMetadata.setAccessible(true);
       
//执行findAutowiringMetadata方法 //参数一:方法所在的对象实例 参数二:方法的参数
//1.收集类中所有加了@AutoWired @Value注解的方法/成员变量
InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor,"bean1",Bean1.class,null);

        这样就拿到了findAutowiringMetadata() 方法的返回,利用方法的返回调用inject()方法:

//2.进行依赖注入
metadata.inject(bean1,"bean1",null);
System.out.println(bean1);

        由此可知:postProcessProperties方法内部做了两件事:

        1.收集类中所有加了@AutoWired @Value注解的方法/成员变量

        2.进行依赖注入

        而inject() 方法内部如何根据类型找到值?

 //1.得到成员变量/方法的信息
 Field bean3 = Bean1.class.getDeclaredField("bean3");
 //2.封装成DependencyDescriptor
 DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(bean3, false);
 //3.调用beanFactory的doResolveDependency
 Object o = beanFactory.doResolveDependency(dependencyDescriptor, null, null, null);
 System.out.println(o);
//方法注入时,以某个方法的参数为单位去容器中找
Method method = Bean1.class.getDeclaredMethod("setBean1", Bean3.class);
DependencyDescriptor descriptor = new DependencyDescriptor(new MethodParameter(method, 0), false);
Object o1 = beanFactory.doResolveDependency(descriptor, null, null, null);
System.out.println(o1);

//方法的参数值注入,以某个方法的参数为单位去容器中找
Method method1 = Bean1.class.getDeclaredMethod("autowiredMethod", String.class);
DependencyDescriptor dependencyDescriptor1 = new DependencyDescriptor(new MethodParameter(method1, 0), false);
Object o2 = beanFactory.doResolveDependency(dependencyDescriptor1, null, null, null);
System.out.println(o2);

        inject内部如何根据类型找到值?

        1.得到成员变量/方法的信息

        2.封装成DependencyDescriptor

        3.调用beanFactory的doResolveDependency

5、BeanFactory 后处理器

        如果需要解析@Bean @Mapper 等注解,则需要使用到BeanFactory的后处理器:

//需要添加对@Bean @ComponentScan @Import 的处理器
context.registerBean(ConfigurationClassPostProcessor.class);

//需要添加对@MapperScan的后处理器
context.registerBean(MapperScannerConfigurer.class, bd -> {
    bd.getPropertyValues().add("basePackage","com.itbaima.a04.mapper");
});
  • ConfigurationClassPostProcessor 可以解析:

    • @ComponentScan

    • @Bean

    • @Import

    • @ImportResource

  • MapperScannerConfigurer 可以解析:

    • @Mapper

        下面会模拟源码中@ComponentScan @Bean @Mapper   后处理器的实现,在进行演示之前,首先介绍一下一些会用到的API和类:

  • CachingMetadataReaderFactory:是 Spring 框架中用于加载和缓存类元数据的工厂类,它提供了一种高效的方式来获取类的元数据信息,并且通过缓存机制提高了性能。当需要获取某个类的元数据时,它会通过底层的 MetadataReader实现类来读取类的信息,并将其封装为MetadataReader对象。
  • AnnotationBeanNameGenerator:是 Spring 框架中用于生成 bean 名称的默认策略类之一。当你不显式地为 bean 指定名称时,Spring 会自动使用它来生成 bean 的名称。这样,你就可以通过在类上添加相应的注解来控制 bean 的名称,而不需要额外进行配置。
  • BeanDefinitionBuilder:是 Spring 框架中用于构建 BeanDefinition对象的工具类。在 Spring 中,BeanDefinition是用于描述和定义 bean 的元数据信息的类,包括了 bean 的类名、作用域、构造函数参数、属性值、初始化方法、销毁方法等等。

其中BeanDefinitionBuilder的一些常用方法:

  • static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass): 创建一个通用的 bean 定义构建器,指定 bean 的类名。
  • BeanDefinitionBuilder setScope(String scope): 设置 bean 的作用域。
  • BeanDefinitionBuilder addPropertyValue(String name, Object value): 添加一个属性值到 bean 的属性列表中。
  • BeanDefinitionBuilder addConstructorArgValue(Object value): 添加一个构造函数参数值到 bean 的构造函数参数列表中。
  • BeanDefinitionBuilder setInitMethodName(String methodName): 设置 bean 的初始化方法名。
  • BeanDefinitionBuilder setDestroyMethodName(String methodName): 设置 bean 的销毁方法名。
  • BeanDefinitionBuilder setLazyInit(boolean lazyInit): 设置是否启用延迟初始化。
  • BeanDefinitionBuilder setAutowireMode(int autowireMode): 设置自动装配模式。
5.1、模拟@ComponentScan 的实现

        找到类中的@ComponentScan 注解

 ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
         if (!ObjectUtils.isEmpty(componentScan)) {
                //得到注解的值
                for (String base : componentScan.basePackages()) {
                    //拼装成路径信息
                    String path = "classpath*:" + base.replace(".","/") + "/**/*.class";
                    //用于读取类的元数据 在运行时加载类的元数据,如类名、注解信息等
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    //用于根据类的注解生成 Bean 的名称
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                    //context的扩展
                    //得到路径下的所有资源是ApplicationContext对于BeanFactory的扩展
                    GenericApplicationContext context = new GenericApplicationContext();
                    Resource[] resource = context.getResources(path);
                    for (Resource r : resource) {
                        //从元数据读取工厂中获取一个元数据读取器reader
                        MetadataReader reader = factory.getMetadataReader(r);
    //                    System.out.println("类名"+reader.getClassMetadata().getClassName());
    //                    System.out.println("是否加了Component"+reader.getAnnotationMetadata().hasAnnotation(Component.class.getName()));
    //                    System.out.println("是否加了Component 相关"+reader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
                        //从元数据读取器reader中获取注解元数据
                        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                        //如果类上加了Component及相关注解
                        if (annotationMetadata.hasMetaAnnotation(Component.class.getName())
                                || annotationMetadata.hasAnnotation(Component.class.getName())) {

                            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(reader.getClassMetadata().getClassName()).getBeanDefinition();
                            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
                            //生成一个唯一的bean名称。
                            String name = generator.generateBeanName(beanDefinition, defaultListableBeanFactory);
                            //通过BeanDefinitionBuilder创建的bean定义(即beanDefinition)注册到Spring容器中。
                            // registerBeanDefinition方法将生成的bean名称(name)与对应的bean定义关联起来,使得该bean可以被Spring容器管理和使用。
                            defaultListableBeanFactory.registerBeanDefinition(name, beanDefinition);

                        }
                    }
                }

            }
5.2、模拟解析 @Bean
//读取@Bean注解
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            //1.读取指定类路径下的信息
            MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/itbaima/a04/Config.class"));
            //2.获取指定类中所有标注了@Bean方法的信息
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
            //遍历这些方法
            for (MethodMetadata method : methods) {
                //使用反射获取注解信息
                String initMothod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
                //用于创建一个通用的 Bean 定义。
                BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
                //设置工厂方法来创建 Bean。使用config中的方法作为工厂方法创建bean
                definitionBuilder.setFactoryMethodOnBean(method.getMethodName(), "config");
                //处理标注了@Bean的有参方法
                definitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                if (initMothod.length()>0){
                    //用于设置 Bean 的初始化方法名。初始化方法是在 Bean 实例化后立即调用的方法。
                    definitionBuilder.setInitMethodName(initMothod);
                }
                //用于获取最终的 Bean 定义对象。
                AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
                DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory)beanFactory;
                //将之前创建的 Bean 定义注册到 Spring 的 Bean 工厂中。     参数一:bean的名称 参数二:用于获取最终的 Bean 定义对象。
                defaultListableBeanFactory.registerBeanDefinition(method.getMethodName(), beanDefinition);
            }
5.3、模拟解析@Mapper

        关于Mapper工厂,需要在参数中指定SqlSessionFactory,以便Spring 容器将指定的依赖注入到方法的参数中。

    @Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

         模拟实现:

        与@Bean类似,被Spring bean管理的类型都是工厂,但是在处理bean名称的时候,需要额外进行,保证bean的名称是每个Mapper接口的名称,而不是工厂的名称。

 try {
            //获取指定路径下的资源
            PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = patternResolver.getResources("classpath:com/itbaima/a04/mapper/**/*.class");
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            for (Resource resource : resources) {
                MetadataReader reader = factory.getMetadataReader(resource);
                ClassMetadata classMetadata = reader.getClassMetadata();
                //判断路径下的某个资源是类还是接口
                if (classMetadata.isInterface()) {
                    //生成接口对应的MapperFactoryBean
                    //被spring管理的bean的类型 是 MapperFactoryBean
                    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                            .genericBeanDefinition(MapperFactoryBean.class)
                            .addConstructorArgValue(classMetadata.getClassName())//给构造设置参数值
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)//按照类型装配,对应方法上的参数
                            .getBeanDefinition();
                    //创建bean名称 此处是根据bean定义去生成名称的,我们此时定义的是bean工厂
                    //所以要在定义一个beanDefinition,名字是Mapper1 Mapper2(具体的接口,非工厂)
                    AbstractBeanDefinition beanDefinitionInter = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                    String name = generator.generateBeanName(beanDefinitionInter, registry);
                    registry.registerBeanDefinition(name, beanDefinition);

                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1591738.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【前缀合】Leetcode 连续数组

题目解析 525. 连续数组 寻找一个子数组&#xff0c;这个子数组中包含相同数目的0和1&#xff0c;但是这个子数组需要最长的 算法讲解 只需在[0,i]寻找一段区间使得这一段区间的和也等于sum即可 细节问题&#xff1a;1. 这里的哈希表的value存的是下标&#xff0c;因为需要找…

ccframe系统的链路追踪,用户ID追踪的实现

需求 之前ccframe cloud V1用的是springcloud微服务&#xff0c;只需要在header将jwttoken一直传下去就没事&#xff0c;最近弄V2转dubbo发现用户id没有自动保存进数据库表。于是开始研究dubbo如何追踪&#xff0c;顺便把链路追踪ID的问题给一并解决掉。 理论 MDC MDC&…

TSINGSEE青犀AI智能分析网关V4吸烟/抽烟检测算法介绍及应用

抽烟检测AI算法是一种基于计算机视觉和深度学习技术的先进工具&#xff0c;旨在准确识别并监测个体是否抽烟。该算法通过训练大量图像数据&#xff0c;使模型能够识别出抽烟行为的关键特征&#xff0c;如烟雾、手部动作和口部形态等。 在原理上&#xff0c;抽烟检测AI算法主要…

跟TED演讲学英文:The dark side of competition in AI by Liv Boeree

The dark side of competition in AI Link: https://www.ted.com/talks/liv_boeree_the_dark_side_of_competition_in_ai Speaker:Liv Boeree Date: October 2023 文章目录 The dark side of competition in AIIntroductionVocabularyTranscriptSummary后记 Introduction Co…

微服务项目——谷粒商城

文章目录 一、项目简介&#xff08;一&#xff09;完整的微服务架构微服务划分图&#xff08;二&#xff09;电商模式1.B2B 模式2.B2C 模式3.C2B 模式4.C2C 模式5.o2o 模式2.谷粒商城 &#xff08;三&#xff09;项目技术&特色&#xff08;四&#xff09;项目前置要求 二、…

Vue3(二):报错调试,vue3响应式原理、computed和watch,ref,props,接口

一、准备工作调试 跟着张天禹老师看前几集的时候可能会遇到如下问题&#xff1a; 1.下载插件&#xff1a;Vue Language Features (Volar)或者直接下载vue-offical 2.npm run serve时运行时出现错误&#xff1a;Error: vitejs/plugin-vue requires vue (&#xff1e;3.2.13) …

基于java+springboot+vue实现的居家养老健康管理系统(文末源码+Lw)23-313

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装智慧社区居家养老健康管理系统软件来发挥其高效地信息处理…

Filebeat+Kafka+ELK 的服务部署

一. kafka 架构深入 1.1 Kafka 工作流程及文件存储机制 Kafka 中消息是以 topic 进行分类的&#xff0c;生产者生产消息&#xff0c;消费者消费消息&#xff0c;都是面向 topic 的。 topic 是逻辑上的概念&#xff0c;而 partition 是物理上的概念&#xff0c;每个 partit…

B004-表达式 类型转换 运算符

目录 表达式数据类型转换自动转换强制转换 运算符数学运算符自增自减运算符i与 i的区别 赋值运算符比较运算符位运算符(了解)逻辑运算符三目运算符 表达式 /*** 表达式定义&#xff1a;由常量 变量 运算符 括号组成的算式&#xff0c;为了按照一定的运算规则计算出结果值* 括…

二叉搜索树--搜索二维矩阵 II

题目描述 编写一个高效的算法来搜索 m * n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,…

基于springboot实现桂林旅游景点导游平台管理系统【项目源码+论文说明】计算机毕业设计

基于springboot实现桂林旅游景点导游平台管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了桂林旅游景点导游平台的开发全过程。通过分析桂林旅游景点导游平台管理的不足&#xff0c;创建了一个计算…

洛谷P1109 学生分组

#先看题目 题目描述 有 n 组学生&#xff0c;给出初始时每组中的学生个数&#xff0c;再给出每组学生人数的上界 R 和下界 L (L≤R)&#xff0c;每次你可以在某组中选出一个学生把他安排到另外一组中&#xff0c;问最少要多少次才可以使 N 组学生的人数都在 [L,R] 中。 输入…

echarts坐标轴、轴线、刻度、刻度标签

坐标轴 x、y轴 x 轴和 y 轴都由轴线、刻度、刻度标签、轴标题四个部分组成。部分图表中还会有网格线来帮助查看和计算数据 普通的二维数据坐标系都有x轴和y轴&#xff0c;通常情况下&#xff0c;x轴显示在图表底部&#xff0c;y轴显示在左侧&#xff0c;一般配置如下&#xf…

[面向对象] 单例模式与工厂模式

单例模式 是一种创建模式&#xff0c;保证一个类只有一个实例&#xff0c;且提供访问实例的全局节点。 工厂模式 面向对象其中的三大原则&#xff1a; 单一职责&#xff1a;一个类只有一个职责&#xff08;Game类负责什么时候创建英雄机&#xff0c;而不需要知道创建英雄机要…

【Linux学习笔记】安卓运行C可执行文件No such file or directory

文章目录 开发环境运行失败现象解决办法方法一&#xff1a;使用静态库方法二&#xff1a;使用动态库创建lib查找依赖库复制需要注意的事情 开发环境 开发板&#xff1a;正点原子RK3568开发板安卓版本&#xff1a;11可执行程序命名&#xff1a;ledApp需加载模块&#xff1a;dts…

npm命令卡在reify:eslint: timing reifyNode:node_modules/webpack Completed in 475ms不动

1.现象 执行npm install命令时&#xff0c;没有报错&#xff0c;卡在reify:eslint: timing reifyNode:node_modules/webpack Completed in 475ms不动 2.解决办法 &#xff08;1&#xff09;更换淘宝镜像源 原淘宝 npm 域名http://npm.taobao.org 和 http://registry.npm.ta…

Day 39:动态规划 LeedCode 62.不同路径 63. 不同路径 II

62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#…

C语言:约瑟夫环问题详解

前言 哈喽&#xff0c;宝子们&#xff01;本期为大家带来一道C语言循环链表的经典算法题&#xff08;约瑟夫环&#xff09;。 目录 1.什么是约瑟夫环2.解决方案思路3.创建链表头结点4.创建循环链表5.删除链表6.完整代码实现 1.什么是约瑟夫环 据说著名历史学家Josephus有过以下…

Ribbon-负载均衡原理解析(案例)

简介&#xff1a;负载均衡&#xff0c;英文名称为Load Balance&#xff0c;其含义就是指将负载&#xff08;工作任务&#xff09;进行平衡、分摊到多个操作单元上进行运行&#xff0c;例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等&#xff0c;从而协同…

MATLAB数据类型和运算符+矩阵创建

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍一、MATLAB数据类型和运算符&#x1f48e;1 MATLAB的数据类型&#x1f339;…