Springboot的手动装配原理
Springboot加载bean的过程分为了
- 手动装配
- 自动装配
两种方式,而手动装配又分为了 - 模式注解装配
- @Enable模块装配
- 条件装配
三种方式,这篇博客主要探讨@Enable模块装配。
既然总结原理,就写个人理解,在分析源码的基础上讨论个人案例,以@EnableWebMvc和@EnableCaching为例。
1.@EnableWebMvc(基于注解驱动实现)
源码分析:
package org.springframework.web.servlet.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
可以很直观看到,其实@EnableWebMvc其实就是为了导入DelegatingWebMvcConfiguration配置类,某种程度上,可以认为@EnableWebMvc其实和@Import({DelegatingWebMvcConfiguration.class})是对等的,只是起了一个有意义的名字而已。
根据原理实现案例:
假设有一个将要被装配的类
public class World {
public String say() {
System.out.println("hello world!");
return "hello world";
}
}
那么我们首先要把这个类在一个配置类中进行注册登记
@Configuration
public class HelloConfig {
@Bean
World world() {
System.out.println("加载HelloConfig");
return new World();
}
}
此时就需要一个用于导入配置类的类,这里不妨起名为EnableHello,也就是jdk源码中的EnableWebMvc
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({HelloConfig.class})//这句最为重要,导入了配置类
public @interface EnableHello {
}
现在我们的注解驱动已经有了,就可以直接使用了
@EnableHello // 引入注解驱动
@RestController
@SpringBootApplication
public class Demo01Application {
// 注入World对象
@Autowired
World world;
public static void main(String[] args) {
SpringApplication.run(Demo01Application.class, args);
}
@GetMapping("/test")
public Object test() {
return "Hello World";
}
}
2.@EnableCaching(基于接口驱动实现)
当我们需要开启springboot项目的缓存功能时候,我们直接打开@EnableCaching注解就可以注入Caching 模块,这时候我们就可以开心使用@Cacheable、@CacheEvict等注解。
源码分析:
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
上面最重要的一句代码就是@Import({CachingConfigurationSelector.class}),你会发现,其实使用@EnableCaching,就是为了导入CachingConfigurationSelector.class这配置类。
而这个CachingConfigurationSelector,其实实现了ImportSelector接口,ImportSelector接口是spring中导入外部配置的核心接口,有一个方法selectImports,其实就是根据EnableCaching的元数据属性(proxyTargetClass、mode、order),选择出需要转配的Configuration。
package org.springframework.context.annotation;
import java.util.function.Predicate;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
根据原理实现案例:
假设有两个用于注册登记bean的配置类HelloConfig1和HelloConfig2,我们就需要一个选择配置类的类HelloConfigSelector,相当于jdk源码中的CachingConfigurationSelector类,我们这里起名叫HelloConfigSelector,让其实现ImportSelector,并重写selectImports方法
public class HelloConfigSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 获取元数据
Map<String, Object> metaData = annotationMetadata.getAnnotationAttributes(EnableHelloSelector.class.getName());
Boolean isLinux = (Boolean) metaData.get("isLinux");
// 由isLinux的值选择配置类
return new String[]{isLinux ? HelloConfig1.class.getName() : HelloConfig2.class.getName()};
}
}
接下来需要一个用于导入选择配置类的类,我们这里叫EnableHelloSelector,相当于jdk源码中的EnableCaching
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({HelloConfigSelector.class})
public @interface EnableHelloSelector {
boolean isLinux();
}
最后我们就可以根据元数据属性选择性条件判断注入需要的配置了
@EnableHelloSelector(isLinux = false)// 由isLinux的值在配置类中的selectImports方法选择注入哪个配置
@RestController
@SpringBootApplication
public class Demo01Application {
public static void main(String[] args) {
SpringApplication.run(Demo01Application.class, args);
}
@GetMapping("/test")
public Object test() {
return "Hello World";
}
}
总结:@EnableCaching比@EnableWebMvc多了一步选择的过程,根据元数据属性选择注入哪个配置,比较灵活。