【手撕Spring源码】一些值得注意的Spring底层细节

news/2024/12/27 7:34:16/

文章目录

  • FactoryBean
  • @Indexed 原理
  • Spring代理的特点
  • @Value 装配底层
  • @Autowired 装配底层
  • 事件监听器
  • 模拟事件发布器

FactoryBean

FactoryBean是一个Factory对象,用于生成其他bean示例。当一个bean实现FactoryBean接口后,Spring容器调用其getObject方法返回该工厂所生成的bean,而不是该FactoryBean本身。但是工厂生成的产品对象只能说是一部分受到Spring容器的管理,我们来看看下面的例子:

我们把一个Bean1通过工厂Bean的方法纳入Spring管理,看看结果:

Bean1:

public class Bean1 implements BeanFactoryAware {private static final Logger log = LoggerFactory.getLogger(Bean1.class);private Bean2 bean2;@Autowiredpublic void setBean2(Bean2 bean2) {log.debug("setBean2({})", bean2);this.bean2 = bean2;}public Bean2 getBean2() {return bean2;}@PostConstructpublic void init() {log.debug("init");}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {log.debug("setBeanFactory({})", beanFactory);}
}

Bean1的工厂类:

@Component("bean1")
public class Bean1FactoryBean implements FactoryBean<Bean1> {private static final Logger log = LoggerFactory.getLogger(Bean1FactoryBean.class);// 决定了根据【类型】获取或依赖注入能否成功@Overridepublic Class<?> getObjectType() {return Bean1.class;}// 决定了 getObject() 方法被调用一次还是多次@Overridepublic boolean isSingleton() {return true;}@Overridepublic Bean1 getObject() throws Exception {Bean1 bean1 = new Bean1();log.debug("create bean: {}", bean1);return bean1;}
}

Bean后处理器:

@Component
public class Bean1PostProcessor implements BeanPostProcessor {private static final Logger log = LoggerFactory.getLogger(Bean1PostProcessor.class);@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("bean1") && bean instanceof Bean1) {log.debug("before [{}] init", beanName);}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("bean1") && bean instanceof Bean1) {log.debug("after [{}] init", beanName);}return bean;}
}

测试类:

@ComponentScan
public class A43 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A43.class);Bean1 bean1 = (Bean1) context.getBean("bean1");Bean1 bean2 = (Bean1) context.getBean("bean1");Bean1 bean3 = (Bean1) context.getBean("bean1");//因为工厂isSingleton方法返回的true,所以是单例模式System.out.println(bean1);System.out.println(bean2);System.out.println(bean3);//如果想通过类型获取,那么工厂中的getObjectType方法就一定要返回正确的值System.out.println(context.getBean(Bean1.class));//两种获取工厂bean的方法System.out.println(context.getBean(Bean1FactoryBean.class));System.out.println(context.getBean("&bean1"));context.close();}

结果:

在这里插入图片描述
在这里插入图片描述

总结💡:

  1. 它的作用是用制造创建过程较为复杂的产品, 如 SqlSessionFactory, 但 @Bean 已具备等价功能
  2. 使用上较为古怪, 一不留神就会用错
    1. 被 FactoryBean 创建的产品
      • 会认为创建、依赖注入、Aware 接口回调、前初始化这些都是 FactoryBean 的职责, 这些流程都不会走
      • 唯有后初始化的流程会走, 也就是产品可以被代理增强
      • 单例的产品不会存储于 BeanFactory 的 singletonObjects 成员中, 而是另一个 factoryBeanObjectCache 成员中
    2. 按名字去获取时, 拿到的是产品对象, 名字前面加 & 获取的是工厂对象。当然我们也可以通过工厂类型去拿工厂对象。

@Indexed 原理

在这里插入图片描述

💡

  1. 在编译时就根据 @Indexed 生成 META-INF/spring.components 文件
  2. 扫描时(二选一)
    • 如果发现 META-INF/spring.components 存在, 以它为准加载 bean definition
    • 否则, 会遍历包下所有 class 资源 (包括 jar 内的)
  3. 解决的问题:在编译期就找到 @Component 组件,节省运行期间扫描 @Component 的时间

Spring代理的特点

我们看一段代码:

@Component
public class Bean1 {private static final Logger log = LoggerFactory.getLogger(Bean1.class);protected Bean2 bean2;protected boolean initialized;@Autowiredpublic void setBean2(Bean2 bean2) {log.debug("setBean2(Bean2 bean2)");this.bean2 = bean2;}@PostConstructpublic void init() {log.debug("init");initialized = true;}public Bean2 getBean2() {log.debug("getBean2()");return bean2;}public boolean isInitialized() {log.debug("isInitialized()");return initialized;}
}

增强:

@Aspect
@Component
public class MyAspect {// 故意对所有方法增强@Before("execution(* com.itheima.a45.Bean1.*(..))")public void before() {System.out.println("before");}
}

测试类:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

从这里我们可以看出来依赖注入和初始化影响的是原始对象

我们再来看看代理与目标的成员变量情况:

    public static void showProxyAndTarget(Bean1 proxy) throws Exception {System.out.println(">>>>> 代理中的成员变量");System.out.println("\tinitialized=" + proxy.initialized);System.out.println("\tbean2=" + proxy.bean2);if (proxy instanceof Advised advised) {System.out.println(">>>>> 目标中的成员变量");Bean1 target = (Bean1) advised.getTargetSource().getTarget();System.out.println("\tinitialized=" + target.initialized);System.out.println("\tbean2=" + target.bean2);}}

在这里插入图片描述

所以说代理与目标是两个对象,二者成员变量并不共用数据。我们平时是使用的方法去获取成员变量,方法虽然被增强,但其中还是会调用原方法,所以获取的就是目标对象中的成员变量。这也给我们一种提示我们想要走代理增强就必须走方法,不能走成员。

  1. spring 代理的设计特点

    • 依赖注入和初始化影响的是原始对象

      • 因此 cglib 不能用 MethodProxy.invokeSuper()
    • 代理与目标是两个对象,二者成员变量并不共用数据

  2. static 方法、final 方法、private 方法均无法增强

    • 进一步理解代理增强基于方法重写,这三种情况是无法被增强的

@Value 装配底层

  1. 查看需要的类型是否为 Optional,是,则进行封装(非延迟),否则向下走
  2. 查看需要的类型是否为 ObjectFactory 或 ObjectProvider,是,则进行封装(延迟),否则向下走
  3. 查看需要的类型(成员或参数)上是否用 @Lazy 修饰,是,则返回代理,否则向下走
  4. 解析 @Value 的值
    1. 如果需要的值是字符串,先解析 ${ },再解析 #{ }
    2. 不是字符串,需要用 TypeConverter 转换
  5. 看需要的类型是否为 Stream、Array、Collection、Map,是,则按集合处理,否则向下走
  6. 在 BeanFactory 的 resolvableDependencies 中找有没有类型合适的对象注入,没有向下走
  7. 在 BeanFactory 及父工厂中找类型匹配的 bean 进行筛选,筛选时会考虑 @Qualifier 及泛型
  8. 结果个数为 0 抛出 NoSuchBeanDefinitionException 异常
  9. 如果结果 > 1,再根据 @Primary 进行筛选
  10. 如果结果仍 > 1,再根据成员名或变量名进行筛选
  11. 结果仍 > 1,抛出 NoUniqueBeanDefinitionException 异常

示例代码:

@Configuration
@SuppressWarnings("all")
public class A46 {public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A46.class);DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();resolver.setBeanFactory(beanFactory);//        test1(context, resolver, Bean1.class.getDeclaredField("home"));
//        test2(context, resolver, Bean1.class.getDeclaredField("age"));
//        test3(context, resolver, Bean2.class.getDeclaredField("bean3"));test3(context, resolver, Bean4.class.getDeclaredField("value"));}private static void test3(AnnotationConfigApplicationContext context, ContextAnnotationAutowireCandidateResolver resolver, Field field) {DependencyDescriptor dd1 = new DependencyDescriptor(field, false);// 获取 @Value 的内容String value = resolver.getSuggestedValue(dd1).toString();System.out.println(value);// 解析 ${}value = context.getEnvironment().resolvePlaceholders(value);System.out.println(value);System.out.println(value.getClass());// 解析 #{} @bean3Object bean3 = context.getBeanFactory().getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(context.getBeanFactory(), null));// 类型转换Object result = context.getBeanFactory().getTypeConverter().convertIfNecessary(bean3, dd1.getDependencyType());System.out.println(result);}private static void test2(AnnotationConfigApplicationContext context, ContextAnnotationAutowireCandidateResolver resolver, Field field) {DependencyDescriptor dd1 = new DependencyDescriptor(field, false);// 获取 @Value 的内容String value = resolver.getSuggestedValue(dd1).toString();System.out.println(value);// 解析 ${}value = context.getEnvironment().resolvePlaceholders(value);System.out.println(value);System.out.println(value.getClass());Object age = context.getBeanFactory().getTypeConverter().convertIfNecessary(value, dd1.getDependencyType());System.out.println(age.getClass());}private static void test1(AnnotationConfigApplicationContext context, ContextAnnotationAutowireCandidateResolver resolver, Field field) {DependencyDescriptor dd1 = new DependencyDescriptor(field, false);// 获取 @Value 的内容String value = resolver.getSuggestedValue(dd1).toString();System.out.println(value);// 解析 ${}value = context.getEnvironment().resolvePlaceholders(value);System.out.println(value);}public class Bean1 {@Value("${JAVA_HOME}")private String home;@Value("18")private int age;}public class Bean2 {@Value("#{@bean3}") // SpringEL       #{SpEL}private Bean3 bean3;}@Component("bean3")public class Bean3 {}static class Bean4 {@Value("#{'hello, ' + '${JAVA_HOME}'}")private String value;}
}

重点:

  • ${ }对应的解析方法:context.getEnvironment().resolvePlaceholders
  • #{ } 对应的解析方法:context.getBeanFactory().getBeanExpressionResolver().evaluate
  • ContextAnnotationAutowireCandidateResolver 作用之一,获取 @Value 的值

@Autowired 装配底层

收获💡

  1. @Autowired 本质上是根据成员变量或方法参数的类型进行装配
  2. 如果待装配类型是 Optional,需要根据 Optional 泛型找到 bean,再封装为 Optional 对象装配
  3. 如果待装配的类型是 ObjectFactory,需要根据 ObjectFactory 泛型创建 ObjectFactory 对象装配
    • 此方法可以延迟 bean 的获取
  4. 如果待装配的成员变量或方法参数上用 @Lazy 标注,会创建代理对象装配
    • 此方法可以延迟真实 bean 的获取
    • 被装配的代理不作为 bean
  5. 如果待装配类型是数组,需要获取数组元素类型,根据此类型找到多个 bean 进行装配
  6. 如果待装配类型是 Collection 或其子接口,需要获取 Collection 泛型,根据此类型找到多个 bean
  7. 如果待装配类型是 ApplicationContext 等特殊类型
    • 会在 BeanFactory 的 resolvableDependencies 成员按类型查找装配
    • resolvableDependencies 是 map 集合,key 是特殊类型,value 是其对应对象
    • 不能直接根据 key 进行查找,而是用 isAssignableFrom 逐一尝试右边类型是否可以被赋值给左边的 key 类型
  8. 如果待装配类型有泛型参数
    • 需要利用 ContextAnnotationAutowireCandidateResolver 按泛型参数类型筛选
  9. 如果待装配类型有 @Qualifier
    • 需要利用 ContextAnnotationAutowireCandidateResolver 按注解提供的 bean 名称筛选
  10. 有 @Primary 标注的 @Component 或 @Bean 的处理
  11. 与成员变量名或方法参数名同名 bean 的处理

事件监听器

事件监听器的两种方式

  1. 实现 ApplicationListener 接口
    • 根据接口泛型确定事件类型
  2. @EventListener 标注监听方法
    • 根据监听器方法参数确定事件类型
    • 解析时机:在 SmartInitializingSingleton(所有单例初始化完成后),解析每个单例 bean

示例代码:

实现 ApplicationListener 接口:

// 事件解耦例子
@Configuration
public class A48_1 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A48_1.class);context.getBean(MyService.class).doBusiness();context.close();}static class MyEvent extends ApplicationEvent {public MyEvent(Object source) {super(source);}}@Componentstatic class MyService {private static final Logger log = LoggerFactory.getLogger(MyService.class);@Autowiredprivate ApplicationEventPublisher publisher; // applicationContextpublic void doBusiness() {log.debug("主线业务");// 主线业务完成后需要做一些支线业务,下面是问题代码publisher.publishEvent(new MyEvent("MyService.doBusiness()"));}}@Componentstatic class SmsApplicationListener implements ApplicationListener<MyEvent> {private static final Logger log = LoggerFactory.getLogger(SmsApplicationListener.class);@Overridepublic void onApplicationEvent(MyEvent event) {log.debug("发送短信");}}@Componentstatic class EmailApplicationListener implements ApplicationListener<MyEvent> {private static final Logger log = LoggerFactory.getLogger(EmailApplicationListener.class);@Overridepublic void onApplicationEvent(MyEvent event) {log.debug("发送邮件");}}}

使用@EventListener:

@Configuration
public class A48_2 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A48_2.class);context.getBean(MyService.class).doBusiness();context.close();}static class MyEvent extends ApplicationEvent {public MyEvent(Object source) {super(source);}}@Componentstatic class MyService {private static final Logger log = LoggerFactory.getLogger(MyService.class);@Autowiredprivate ApplicationEventPublisher publisher; // applicationContextpublic void doBusiness() {log.debug("主线业务");// 主线业务完成后需要做一些支线业务,下面是问题代码publisher.publishEvent(new MyEvent("MyService.doBusiness()"));}}@Componentstatic class SmsService {private static final Logger log = LoggerFactory.getLogger(SmsService.class);@EventListenerpublic void listener(MyEvent myEvent) {log.debug("发送短信");}}@Componentstatic class EmailService {private static final Logger log = LoggerFactory.getLogger(EmailService.class);@EventListenerpublic void listener(MyEvent myEvent) {log.debug("发送邮件");}}//模拟多线程	@Beanpublic ThreadPoolTaskExecutor executor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(3);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);return executor;}@Beanpublic SimpleApplicationEventMulticaster applicationEventMulticaster(ThreadPoolTaskExecutor executor) {SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();multicaster.setTaskExecutor(executor);return multicaster;}}

模拟事件发布器

    @Beanpublic ApplicationEventMulticaster applicationEventMulticaster(ConfigurableApplicationContext context, ThreadPoolTaskExecutor executor) {return new AbstractApplicationEventMulticaster() {private List<GenericApplicationListener> listeners = new ArrayList<>();// 收集监听器public void addApplicationListenerBean(String name) {ApplicationListener listener = context.getBean(name, ApplicationListener.class);System.out.println(listener);// 获取该监听器支持的事件类型ResolvableType type = ResolvableType.forClass(listener.getClass()).getInterfaces()[0].getGeneric();System.out.println(type);// 将原始的 listener 封装为支持事件类型检查的 listenerGenericApplicationListener genericApplicationListener = new GenericApplicationListener() {// 是否支持某事件类型                真实的事件类型public boolean supportsEventType(ResolvableType eventType) {return type.isAssignableFrom(eventType);}public void onApplicationEvent(ApplicationEvent event) {executor.submit(() -> listener.onApplicationEvent(event));}};listeners.add(genericApplicationListener);}// 发布事件public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {for (GenericApplicationListener listener : listeners) {if (listener.supportsEventType(ResolvableType.forClass(event.getClass()))) {listener.onApplicationEvent(event);}}}};}

为了只实现两个核心方法,我们创建了一个AbstractApplicationEventMulticaster对ApplicationEventMulticaster接口进行了空实现,并定义为抽象类:
在这里插入图片描述

事件发布器模拟实现

  1. addApplicationListenerBean 负责收集容器中的监听器
    • 监听器会统一转换为 GenericApplicationListener 对象,以支持判断事件类型
  2. multicastEvent 遍历监听器集合,发布事件
    • 发布前先通过 GenericApplicationListener.supportsEventType 判断支持该事件类型才发事件
    • 可以利用线程池进行异步发事件优化
  3. 如果发送的事件对象不是 ApplicationEvent 类型,Spring 会把它包装为 PayloadApplicationEvent 并用泛型技术解析事件对象的原始类型

http://www.ppmy.cn/news/273188.html

相关文章

迈科龙软件测试笔试题

昨天去笔试&#xff0c;记下一些。有选择题、判断题、填空题、性能测试题、SQL题、案例设计题 1、考察黑盒白盒测试的一些理论知识 2、软件测试模型 3、选择题、判断题、填空题都是理论&#xff0c;比较基础 4、SQL考察多表查询、简单的增删改查 5、性能测试题考察性能指标…

JAVA中对象的克隆

对象克隆 Object类中定义一个clone方法 protected native Object clone() throws CloneNotSupportedException;native方法用于声明一个非java语言实现的代码&#xff0c;供java程序调用。因为java语言程序是运行在JVM上&#xff0c; 如果要访问比较底层的与操作系统相关的方法…

VScode配置用户代码片段

一、配置步骤 二、代码内容 vsCode 代码片段 自动生成格式 "Print to console": {"prefix": "v1","body": ["new Vue({","\tel: #root,","\tdata: {","","\t}","})"],…

关于空调安装时外机支架另收费

这里本是程序员的天地&#xff0c;应该是程序相关的东西&#xff0c;也拿来记录点无关的东西&#xff1b; 但从另一个角度&#xff0c;程序员不是人吗&#xff1f;不需要生活吗&#xff1f;不用管生活方方面面的琐碎琐碎琐碎吗&#xff1f; 刚买了个空调&#xff0c;结果接到电…

vue

箭头函数的this 指向全局 vue里是json this 指向window vue不能使用箭头函数 v-model 双向数据绑定 拿到的input的值是 字符串 parseInt 可以把字符串转换成 number v-model v-if v-show v-text v-html v-for v-clock v-clock click.stop 是阻止冒泡 computer watch rout…

产品读书《大败局 I》 《大败局II》

关键词&#xff1a;广告、政商、股市、资本市场和欲望。 时间&#xff1a; 《大败局1》记录的败局均发生在1989年到2000年之间。 《大败局2》记录的败局均发生在2000年到2007之间。 人物&#xff1a; 《大败局1》中出现的企业家更多的是草莽式的身份&#xff0c;退伍军人姬长孔…

UPS电源报警器一直响是什么问题?

UPS电源是一种高可靠性的电源设备&#xff0c;但依然会存在UPS出现故障的情况&#xff0c;出现故障时电源报警器就会响&#xff0c;那么UPS电源报警器为什么响呢&#xff1f; UPS电源报警的原因 1.逆变器与市电不同步&#xff0c;如果同时出现A03之警报&#xff0c;则表示本警报…

毕业设计之科龙集团校园招聘系统测试计划大纲[更新]

毕业设计测试计划大纲 1&#xff0e; 简介 测试计划意在完善测试对象&#xff0c;掌握测试时机。更透彻的分析系统的各个环节&#xff0c;有助于完善系统功能&#xff0c;提高系统性能及稳定性、控制软件产品质量。 1. 1目的 <企业信息化招聘系统>该文档有助于实…