spring高级篇(一)
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);//得到BeanFactoryConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();//解析beanFactory中有多少单例的beanMap<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);@Autowiredprivate 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);@EventListenerpublic void sendMessage(UserRegisterEvent event){log.debug(event.toString());log.info("发送短信");}
}
//例:通过反射得到存放bean的数组Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");singletonObjects.setAccessible(true);//得到BeanFactoryConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();//解析beanFactory中有多少单例的beanMap<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);@Autowiredprivate 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);@EventListenerpublic 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常见的实现:
-
XmlBeanFactory: XmlBeanFactory是 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*/@Configurationstatic class config {@Beanpublic Bean1 createBean1() {return new Bean1();}@Beanpublic Bean2 createBean2() {return new Bean2();}@Beanpublic Bean3 createBean3() {return new Bean3();}@Beanpublic Bean4 createBean4() {return new Bean4();}}interface Inter{}static class Bean1 {private static final Logger log = LoggerFactory.getLogger(Bean1.class);@AutowiredBean2 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.internalEventListenerFactorySystem.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配置类实现
会默认加上后处理器:
@Configurationstatic class Config{@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic 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);}
@Configurationstatic class WebConfig{/*** 将内嵌tomcat注册成bean* @return*/@Beanpublic ServletWebServerFactory servletWebServerFactory() {return new TomcatServletWebServerFactory();}@Beanpublic DispatcherServlet dispatcherServlet(){return new DispatcherServlet();}@Beanpublic 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 生命周期通常包括以下几个阶段:
-
实例化(Instantiation):容器根据配置信息(XML、注解等)创建 bean 的实例。这通常涉及到实例化目标 bean 类,并根据配置注入依赖。
-
属性设置(Population):容器通过依赖注入(Dependency Injection)将配置的属性值或引用设置到 bean 实例中。这包括基本属性、引用类型、集合类型等。
-
初始化(Initialization):
- Initialization callbacks:在 bean 实例化完成并且所有属性设置完毕后,容器会调用 bean 的InitializingBean接口的afterPropertiesSet()方法,或者调用在配置中声明的初始化方法。
- BeanPostProcessors:在调用初始化方法之前和之后,容器会调用所有注册的 BeanPostProcessor实现类的postProcessBeforeInitialization
()
和postProcessAfterInitialization()方法,允许对 bean 进行自定义的初始化处理。
-
使用(In Use):在初始化完成后,bean 可以被容器或其他 bean 使用。
-
销毁(Destruction):
- DisposableBean:如果 bean 实现了 DisposableBean接口,容器在销毁 bean 之前会调用其destroy() 方法。
- 自定义销毁方法:在配置中可以声明自定义的销毁方法,在容器关闭时调用。
-
销毁回调(Destruction callbacks):与初始化类似,容器在销毁 bean 之前会调用所有注册的 BeanPostProcessor实现类的 postProcessBeforeDestruction
()
方法,允许对 bean 进行自定义的销毁处理。
@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {private static final Logger log = LoggerFactory.getLogger(MyBeanPostProcessor.class);@Overridepublic void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {if (beanName.equals("lifeCycleBean")) {log.debug("<<<<<< 销毁之前执行, 如 @PreDestroy");}}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {if (beanName.equals("lifeCycleBean")) {log.debug("<<<<<< 实例化之前执行, 这里返回的对象会替换掉原本的 bean");}return null;}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {if (beanName.equals("lifeCycleBean")) {log.debug("<<<<<< 实例化之后执行, 这里如果返回 false 会跳过依赖注入阶段");
// return false;}return true;}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {if (beanName.equals("lifeCycleBean")) {log.debug("<<<<<< 依赖注入阶段执行, 如 @Autowired、@Value、@Resource");}return pvs;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("lifeCycleBean")) {log.debug("<<<<<< 初始化之前执行, 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties");}return bean;}@Overridepublic 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;@Autowiredpublic void setBean1(Bean3 bean3) {log.debug("setBean3");this.bean3 = bean3;}@Resourcepublic void setBean2(Bean2 bean2) {log.debug("setBean2");this.bean2 = bean2;}@Autowiredpublic void autowiredMethod(@Value("${JAVA_HOME}")String name){log.debug("注入:"+name);}@PostConstructpublic void initMethod(){log.debug("初始化");}@PreDestroypublic 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.封装成DependencyDescriptorDependencyDescriptor dependencyDescriptor = new DependencyDescriptor(bean3, false);//3.调用beanFactory的doResolveDependencyObject 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) {//从元数据读取工厂中获取一个元数据读取器readerMetadataReader 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中的方法作为工厂方法创建beandefinitionBuilder.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 容器将指定的依赖注入到方法的参数中。
@Beanpublic 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的类型 是 MapperFactoryBeanAbstractBeanDefinition 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);}
相关文章:

spring高级篇(一)
1、ApplicationContext与BeanFactory BeanFactory是ApplicationContext的父级接口:(citlaltu查看类关系图) 在springboot的启动类中,我们通过SpringApplication.run方法拿到的是继承了ApplicationContext的ConfigurableApplicatio…...

免费的GPT-3.5 API服务aurora
什么是 aurora ? aurora 是利用免登录 ChatGPT Web 提供的无限制免费 GPT-3.5-Turbo API 的服务,支持使用 3.5 的 access 调用。 【注意】:仅 IP 属地支持免登录使用 ChatGPT的才可以使用(也可以自定义 Baseurl 来绕过限制&#x…...
突破编程_C++_网络编程(Windows 套接字(处理 TCP 粘包问题))
1 TCP 协议与粘包问题概述 1.1 TCP 粘包的产生原因 TCP粘包问题的产生原因涉及多个方面,主要的原因如下: 首先,发送方在发送数据时,由于TCP协议为提高传输效率而采用的Nagle算法,可能会将多个小数据包合并成一个大数…...

【训练营】DateWhale——动手学大模型应用开发(更新中)
文章目录 写在前面大模型简介LLM简介RAG简介LangChain开发框架开发LLM应用的整体流程 写在前面 大模型时代从GPT爆发开始到现在已有一年多了,深度学习发展之快无法想象,一味感叹技术发展速度超越个人学习速度是没用的,倒不如花点时间参加一些…...

【学习笔记十九】EWM Yard Management概述及后台配置
一、EWM Yard堆场管理业务概述 1.Yard Management基本概念 YARD管理针对的是库房以外的区域,可以理解为入大门开始到库门之前的这部分的区域 堆场结构 像在仓库中一样,将相应仓位映射为堆场仓位,可将其分组到堆场分区。场地中可能具有以下结…...

【环境搭建】(五)Ubuntu22.04安装cuda_11.8.0+cudnn_8.6.0
一个愿意伫立在巨人肩膀上的农民...... 设备配置: 一、安装GCC 安装cuda之前,首先应该安装GCC,安装cuda需要用到GCC,否则报错。可以先使用下方指令在终端查看是否已经安装GCC。 gcc --version 如果终端打印如下则说明已经安装…...

【UE5.1】使用MySQL and MariaDB Integration插件——(3)表格形式显示数据
在上一篇(【UE5.1】使用MySQL and MariaDB Integration插件——(2)查询)基础上继续实现以表格形式显示查询到的数据的功能 效果 步骤 1. 在“WBP_Query”中将多行文本框替换未网格面板控件,该控件可以用表格形式布局…...

JVM复习
冯诺依曼模型与计算机处理数据过程相关联: 冯诺依曼模型: 输入/输出设备存储器输出设备运算器控制器处理过程: 提取阶段:输入设备传入原始数据,存储到存储器解码阶段:由CPU的指令集架构ISA将数值解…...
63、ARM/STM32中IIC相关学习20240417
完成温湿度传感器数据采集实验。 【思路:1.通过IIC通信原理,理解其通信过程,通过调用封装的IIC函数达成主机和从机之间:起始信号、终止信号、读、写数据的操作; 2.了解温湿度传感器控制芯片SI7006的工作原理&#…...

离岸人民币与人民币国际化
参考 什么是离岸人民币?它有什么用? - 知乎 “人民币就是人民币,为什么要在它前面加上离岸二字?” “既然有离岸人民币,是否有在岸人民币?” 今天我们就简单了解一下什么是离岸人民币。 离岸/在岸人民币…...
Linux平台上部署和运行Ollama的全面指南
Ollama的安装与配置 Ollama提供了一种简单的安装方法,只需一行命令即可完成安装,但是对于想要更深入了解和自定义安装的用户,我们也提供了手动安装的步骤。 快速安装 Ollama的安装极为简单,只需在终端中执行以下命令࿱…...
Web---robots协议详解
在Web中,robots协议(也称为robots.txt)是一种文本文件,用于向搜索引擎机器人(通常称为爬虫)提供指导,以指示它们哪些页面可以抓取,哪些页面应该忽略。robots.txt文件位于网站的根目录…...

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第四套
华为海思校园招聘-芯片-数字 IC 方向 题目分享——第四套 (共9套,有答案和解析,答案非官方,仅供参考)(共九套,每套四十个选择题) 部分题目分享,完整版获取(WX:didadida…...
clipper一些数据结构(入门初识(一))
clipper一些数据结构(一) Clipper库是一个用于执行多边形裁剪(clipping)和偏移(offsetting)操作的开源C库。在Clipper库中,点和多边形(polygon)是基本的数据结构。Clipp…...

读《SQL基础教程 第二版 上》的一些总结
1. 数据库语言 DDL: Data Definition Language,数据定义语言(库、表的操作) DML: Data Manipulation Language, 数据操控语言(对表中数据的增删改) DQL: Data Query Language,数据库查询语言…...

EDI是什么:EDI系统功能介绍
EDI全称Electronic Data Interchange,中文名称是电子数据交换,也被称为“无纸化贸易”。EDI实现企业间(B2B)自动化通信,帮助贸易伙伴和组织完成更多的工作、加快物流时间并消除人为错误。 目前国内企业实现EDI通信大多…...

64B/66B GT Transceiver 配置
一、前言 前一篇文章已经讲述了64B/66B的编码原理,此篇文章来配置一下7系列GT的64B/66B编码。并讲述所对应的例子工程的架构,以及部分代码的含义。 二、IP核配置 1、打开7 Series FPGAs Transceiver Wizards,选择将共享逻辑放置在example …...

ES6: promise对象与回调地狱
ES6: promise对象与回调地狱 一、回调地狱二、Promise概述三、Promise的组成四、用函数封装Promise读取文件操作 一、回调地狱 在js中大量使用回调函数进行异步操作,而异步操作什么时候返回结果是不可控的,所以希望一段程序按我们制定的顺序执…...
Qt事件处理机制2-事件函数的传播
所有继承自QObject的类都有event函数,该函数用来处理自身的事件,函数定义如下: virtual bool QObject::event(QEvent *e);Qt帮助文档: This virtual function receives events to an object and should return true i…...

【PDF.js】PDF文件预览
【PDF.js】PDF文件预览 一、PDF.js二、PDF.js 下载1、下载PDF.js2、在项目中引入3、屏蔽跨域错误 三、项目中使用四、说明五、实现效果 使用PDFJS实现pdf文件的预览,支持预览指定页、关键词搜索、缩略图、页面尺寸调整等等。 一、PDF.js 官方地址 文档地址 二、PD…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...