一些值得注意的Spring细节

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: FactoryBeanFactoryBean是一个Factory对象,用于生成其他bean示例。当一个bean实现FactoryBean接口后,Spring容器调用其getObject方法返回该工厂所生成的bean,而不是该FactoryBean本身。但是工厂生成的产品对象只能说是一部分受到Spring容器的管理,我们来看看下面的例子:我们把一个Bean1通过工厂Bean的方法纳入Spring管理,看看结果:Bean1:public class Bean1 implements BeanFactoryAware { private static final Logger

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;

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

public Bean2 getBean2() {
    return bean2;
}

@PostConstruct
public void init() {
    log.debug("init");
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    log.debug("setBeanFactory({})", beanFactory);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Bean1的工厂类:

@Component("bean1")
public class Bean1FactoryBean implements FactoryBean {

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

// 决定了根据【类型】获取或依赖注入能否成功
@Override
public Class<?> getObjectType() {
    return Bean1.class;
}

// 决定了 getObject() 方法被调用一次还是多次
@Override
public boolean isSingleton() {
    return true;
}

@Override
public Bean1 getObject() throws Exception {
    Bean1 bean1 = new Bean1();
    log.debug("create bean: {}", bean1);
    return bean1;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Bean后处理器:

@Component
public class Bean1PostProcessor implements BeanPostProcessor {

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

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (beanName.equals("bean1") && bean instanceof Bean1) {
        log.debug("before [{}] init", beanName);
    }
    return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (beanName.equals("bean1") && bean instanceof Bean1) {
        log.debug("after [{}] init", beanName);
    }
    return bean;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
测试类:

@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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
结果:

总结💡:

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

💡

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

@Component
public class Bean1 {

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

protected Bean2 bean2;

protected boolean initialized;

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

@PostConstruct
public void init() {
    log.debug("init");
    initialized = true;
}

public Bean2 getBean2() {
    log.debug("getBean2()");
    return bean2;
}

public boolean isInitialized() {
    log.debug("isInitialized()");
    return initialized;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
增强:

@Aspect
@Component
public class MyAspect {

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

}
1
2
3
4
5
6
7
8
9
10
测试类:

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

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

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
2
3
4
5
6
7
8
9
10
11
12

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

spring 代理的设计特点

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

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

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

进一步理解代理增强基于方法重写,这三种情况是无法被增强的
@Value 装配底层
查看需要的类型是否为 Optional,是,则进行封装(非延迟),否则向下走
查看需要的类型是否为 ObjectFactory 或 ObjectProvider,是,则进行封装(延迟),否则向下走
查看需要的类型(成员或参数)上是否用 @Lazy 修饰,是,则返回代理,否则向下走
解析 @Value 的值
如果需要的值是字符串,先解析 ${ },再解析 #{ }
不是字符串,需要用 TypeConverter 转换
看需要的类型是否为 Stream、Array、Collection、Map,是,则按集合处理,否则向下走
在 BeanFactory 的 resolvableDependencies 中找有没有类型合适的对象注入,没有向下走
在 BeanFactory 及父工厂中找类型匹配的 bean 进行筛选,筛选时会考虑 @Qualifier 及泛型
结果个数为 0 抛出 NoSuchBeanDefinitionException 异常
如果结果 > 1,再根据 @Primary 进行筛选
如果结果仍 > 1,再根据成员名或变量名进行筛选
结果仍 > 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());

    // 解析 #{} @bean3
    Object 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;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
重点:

${ }对应的解析方法:context.getEnvironment().resolvePlaceholders

{ } 对应的解析方法:context.getBeanFactory().getBeanExpressionResolver().evaluate

ContextAnnotationAutowireCandidateResolver 作用之一,获取 @Value 的值
@Autowired 装配底层
收获💡

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

实现 ApplicationListener 接口
根据接口泛型确定事件类型
@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);
    }
}

@Component
static class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);
    @Autowired
    private ApplicationEventPublisher publisher; // applicationContext
    public void doBusiness() {
        log.debug("主线业务");
        // 主线业务完成后需要做一些支线业务,下面是问题代码
        publisher.publishEvent(new MyEvent("MyService.doBusiness()"));
    }
}

@Component
static class SmsApplicationListener implements ApplicationListener<MyEvent> {
    private static final Logger log = LoggerFactory.getLogger(SmsApplicationListener.class);
    @Override
    public void onApplicationEvent(MyEvent event) {
        log.debug("发送短信");
    }
}

@Component
static class EmailApplicationListener implements ApplicationListener<MyEvent> {
    private static final Logger log = LoggerFactory.getLogger(EmailApplicationListener.class);
    @Override
    public void onApplicationEvent(MyEvent event) {
        log.debug("发送邮件");
    }
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
使用@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);
    }
}

@Component
static class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);
    @Autowired
    private ApplicationEventPublisher publisher; // applicationContext
    public void doBusiness() {
        log.debug("主线业务");
        // 主线业务完成后需要做一些支线业务,下面是问题代码
        publisher.publishEvent(new MyEvent("MyService.doBusiness()"));
    }
}

@Component
static class SmsService {
    private static final Logger log = LoggerFactory.getLogger(SmsService.class);
    @EventListener
    public void listener(MyEvent myEvent) {
        log.debug("发送短信");
    }
}

@Component
static class EmailService {
    private static final Logger log = LoggerFactory.getLogger(EmailService.class);
    @EventListener
    public void listener(MyEvent myEvent) {
        log.debug("发送邮件");
    }
}

//模拟多线程    

@Bean
public ThreadPoolTaskExecutor executor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(3);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(100);
    return executor;
}

@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(ThreadPoolTaskExecutor executor) {
    SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
    multicaster.setTaskExecutor(executor);
    return multicaster;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
模拟事件发布器
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(ConfigurableApplicationContext context, ThreadPoolTaskExecutor executor) {
return new AbstractApplicationEventMulticaster() {
private List 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 封装为支持事件类型检查的 listener
            GenericApplicationListener 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);
                }
            }
        }
    };
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
为了只实现两个核心方法,我们创建了一个AbstractApplicationEventMulticaster对ApplicationEventMulticaster接口进行了空实现,并定义为抽象类:

事件发布器模拟实现

addApplicationListenerBean 负责收集容器中的监听器
监听器会统一转换为 GenericApplicationListener 对象,以支持判断事件类型
multicastEvent 遍历监听器集合,发布事件
发布前先通过 GenericApplicationListener.supportsEventType 判断支持该事件类型才发事件
可以利用线程池进行异步发事件优化
如果发送的事件对象不是 ApplicationEvent 类型,Spring 会把它包装为 PayloadApplicationEvent 并用泛型技术解析事件对象的原始类型
————————————————
版权声明:本文为CSDN博主「十八岁讨厌编程」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zyb18507175502/article/details/131086481

目录
相关文章
|
XML Java 数据格式
深入探究Spring核心模块
深入探究Spring核心模块
50 0
|
6月前
|
负载均衡 Java API
|
6月前
|
Java 数据库连接 Spring
Spring Boot常见企业开发场景应用、自动配置原理结构分析(二)
Spring Boot常见企业开发场景应用、自动配置原理结构分析
|
6月前
|
安全 Java Maven
Spring Boot常见企业开发场景应用、自动配置原理结构分析(三)
Spring Boot常见企业开发场景应用、自动配置原理结构分析
|
6月前
|
前端开发 Java 数据库连接
Spring Boot常见企业开发场景应用、自动配置原理结构分析(一)
Spring Boot常见企业开发场景应用、自动配置原理结构分析
324 0
|
前端开发 Java 开发工具
30个类手写Spring核心原理之环境准备(1)
IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多。Lombok是开源的代码生成库,是一款非常实用的小工具,我们在编辑实体类时可以通过Lombok注解减少getter、setter等方法的编写,在更改实体类时只需要修改属性即可,减少了很多重复代码的编写工作。 首先需要安装IntelliJ IDEA中的Lombok插件,打开IntelliJ IDEA后单击菜单栏中的File→Settings(如下图所示),或者使用快捷键Ctrl+Alt+S进入设置界面。
85 0
|
消息中间件 缓存 安全
详细分析 Spring Boot 的启动流程,其内部机制和关键组件是怎样的?
详细分析 Spring Boot 的启动流程,其内部机制和关键组件是怎样的?
119 0
|
Java 测试技术 Maven
【Spring学习笔记 二】构建第一个Sping框架程序
【Spring学习笔记 二】构建第一个Sping框架程序
62 0
|
设计模式 Java Spring
【Spring】核心部分之AOP:通过列举代码例子,从底层刨析,深入源码,轻轻松松理解Spring的核心AOP,AOP有这一篇足以
【Spring】核心部分之AOP:通过列举代码例子,从底层刨析,深入源码,轻轻松松理解Spring的核心AOP,AOP有这一篇足以
|
XML Java 数据格式
【Spring】核心部分之IOC:通过列举代码例子,从底层刨析,深入源码,轻轻松松理解Spring的核心IOC,IOC有这一篇足以
【Spring】核心部分之IOC:通过列举代码例子,从底层刨析,深入源码,轻轻松松理解Spring的核心IOC,IOC有这一篇足以
112 0