模式注解是一种用于声明在应用中扮演“组件”角色的注解。如 Spring Framework 中的 @Repository
标注在任何类上 ,用于扮演仓储角色的模式注解。
@Component
作为一种由 Spring 容器托管的通用模式组件,任何被 @Component
标注的组件均为组件扫描的候选对象。类似地,凡是被 @Component
元标注(meta-annotated)的注解,如 @Service ,当任何组件标注它时,也被视作组件扫
描的候选对象
Spring Framework 注解 | 场景说明 | 起始版本 |
---|---|---|
@Repository | 数据仓储模式注解 | 2.0 |
@Component | 通用组件模式注解 | 2.5 |
@Service | 服务模式注解 | 2.5 |
@Controller | Web 控制器模式注解 | 25 |
@Configuration | 配置类模式注解 | 3.0 |
以上其余四个注解,都标注了@Component
/**
* 一级 {@link Repository @Repository}
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repository
public @interface FirstLevelRepository {
String value() default "";
}
@Component
@Repository
使用自定义注解@FirstLevelRepository
的类也会被注册到容器中去
/**
* {@link FirstLevelRepository}
*/
@FirstLevelRepository(value = "myFirstLevelRepository")
public class MyFirstLevelRepository {
}
/**
* 二级 {@link FirstLevelRepository}
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@FirstLevelRepository
public @interface SecondLevelRepository {
String value() default "";
}
@Component
@Repository
@FirstLevelRepository
使用自定义注解@SecondLevelRepository
的类也会被注册到容器中去
/**
* {{@link SecondLevelRepository}}
* Created by Yuk on 2019/3/9.
*/
@SecondLevelRepository(value = "mySecondLevelRepository")
public class MySecondLevelRepository {
}
需要有一种配置方式来发现这些被模式注解标注的类
@ComponentScan(basePackages = "com.yk.dive.in.spring.boot")
public class SpringConfiguration {
...
}
Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立的单元。比如 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处理)模块等
框架实现 | @Enable 注解模块 | 激活模块 |
---|---|---|
Spring Framework | @EnableWebMvc | Web MVC 模块 |
- | @EnableTransactionManagement | 事务管理模块 |
- | @EnableCaching | Caching 模块 |
- | @EnableMBeanExport | JMX模块 |
- | @EnableAsync | 异步处理模块 |
- | @EnableWebFlux | Web Flux模块 |
- | @EnableAspectAutoProxy | AspectJ代理模块 |
Spring Framework的@Enable
具是通过@Import
引入来实现的,具体可以看我之前写的一篇spring源码spring加载流程之ConfigurationClassPostProcessor其中有处理@Import
注解的详细讲解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration
public class DelegatingWebMvcConfiguration extends
WebMvcConfigurationSupport {
...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
...
}
public class CachingConfigurationSelector extends AdviceModeImportSelector {
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
}
其中AdviceModeImportSelector
实现了ImportSelector
接口,所以要实现selectImports
方法
/**
* HelloWorld 配置
*/
@Configuration
public class HelloWorldConfiguration {
@Bean
public String helloWorld(){
return "hello world 2018";
}
}
自定义的配置不能再@ComponentScan
的扫描范围内,否则就毫无意义了
/**
* 激活 HelloWorld 模块
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
在启动配置类加上@EnableHelloWorld
注解,就可以注入helloWorld
需要注入的Bean
public class HelloWorld {
}
/**
* HelloWorld {@link ImportSelector}
*/
public class HelloWorldImportSelector implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根据importingClassMetadata条件,可自定义实现返回
return new String[]{HelloWorld.class.getName()};
}
}
/**
* 激活 HelloWorld 模块
*
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
在启动配置类加上@EnableHelloWorld
注解,就可以注入helloWorld
从 Spring Framework 3.1 开始,允许在 Bean 装配时增加前置条件判断
Spring 注解 | 场景说明 | 起始版本 |
---|---|---|
@Profile | 配置化条件装配 | 3.1 |
@Conditional | 编程条件装配 | 4.0 |
计算服务,多整数求和 sum
@Profile(“Java7”) : for 循环
@Profile(“Java8”) : Lambda
/**
* 计算服务
*/
public interface CalculateService {
/**
* 多个整数求和
* @param values
* @return sum 累加值
*/
public Integer sum(Integer... values);
}
/**
* java7 for循环实现 {@link CalculateService}
*/
@Profile("java7")
@Service
public class Java7CalculateService implements CalculateService{
@Override
public Integer sum(Integer... values) {
System.out.println("java7 for循环实现");
int sum = 0;
for (int i = 0; i < values.length; i++){
sum += values[i];
}
return sum;
}
}
/**
* java8 lambda表达式实现 {@link CalculateService}
*/
@Profile("java8")
@Service
public class Java8CalculateService implements CalculateService{
@Override
public Integer sum(Integer... values) {
System.out.println("java8 lambda表达式实现");
int sum = Stream.of(values).reduce(0,Integer::sum);
return sum;
}
}
@SpringBootApplication(scanBasePackages = {"com.imooc.diveinspringboot.service"})
public class DiveInSpringBootApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(DiveInSpringBootApplication.class)
.web(WebApplicationType.NONE)
.run();
CalculateService service = context.getBean(CalculateService.class);
System.out.println("CalculateService.sum(1...10):"+
service.sum(1,2,3,4,5,6,7,8,9,10));
context.close();
}
}
在启动时没有配置.profiles()
,直接报错No qualifying bean of type 'com.imooc.diveinspringboot.service.CalculateService' available
那么加上.profiles("java7")
,就会装载Java7CalculateService
到容器
自定义注解@ConditionalOnSystemProperty
,@Conditional
就是判断配置是否符合条件
/**
* 系统属性条件判断 {@link OnSystemPropertyConditional}
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSystemPropertyConditional.class)
public @interface ConditionalOnSystemProperty {
/**
* Java 系统属性名称
* @return
*/
String name();
/**
* Java 系统属性值
* @return
*/
String value();
}
实现接口Condition
/**
* 实现Condition
* 实现方法matches返回true或false
*/
public class OnSystemPropertyConditional implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取注解的属性
Map map = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
String propertyName = (String) map.get("name");
String propertyValue= (String) map.get("value");
String javaPropertyValue = System.getProperty(propertyName);
System.out.println("javaPropertyValue:"+javaPropertyValue);
System.out.println("propertyValue:"+propertyValue);
return propertyValue.equals(javaPropertyValue);
}
}
/**
* 判断引导类 {@link ConditionalOnSystemProperty}
*
* Created by Yuk on 2018/12/2.
*/
public class ConditionalOnSystemPropertyBootstrap {
@Bean
@ConditionalOnSystemProperty(name = "user.name",value = "yk")
public String helloWorld(){
return "hello world.";
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
String helloWorld = context.getBean("helloWorld",String.class);
System.out.println(helloWorld);
context.close();
}
}
user.name
在系统中是电脑名称,我的user是“浴缸”,而我传的value是“yk”,matches
方法肯定返回false,也就不会注入helloWorld
,直接报错:
No bean named 'helloWorld' available
修改一下配置
@Bean
@ConditionalOnSystemProperty(name = "user.name",value = "浴缸")
public String helloWorld(){
return "hello world.";
}
在 Spring Boot 场景下,基于约定大于配置的原则,实现 Spring 组件自动装配的目的。其中使用了
Spring 模式注解装配
Spring @Enable 模块装配
Spring 条件装配
Spring 工厂加载机制
框架实现 | @Enable 注解模块 | 激活模块 |
---|---|---|
Spring Boot | @EnableAutoConfiguration | 自动装配模块 |
- | @EnableAutoConfiguration | Actuator 管理模块 |
- | @EnableManagementContext | Caching 模块 |
- | @EnableConfigurationProperties | 配置属性绑定模块 |
- | @EnableOAuth2Sso OAuth2 | 单点登录模块 |
参考 META-INF/spring.factories
spring boot启动时,SpringFactoriesLoader
的loadFactories方法会找到所有的 META-INF/spring.factories,并加载里面的配置,是以key-value的形式配置了需要装配的类。
@SpringBootApplication
包含了@EnableAutoConfiguration
/**
* 自动装配引导类 {@link HelloWorldAutoConfiguration}
*/
@EnableAutoConfiguration // 激活自动装配
public class EnableAutoConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
HelloWorld helloWorld = context.getBean(HelloWorld.class);
System.out.println("helloWorld Bean:"+helloWorld);
context.close();
}
}
/**
* 实现自动装配
*
* Created by Yuk on 2018/12/2.
*/
@Configuration // Spring 模式注解
@EnableHelloWorld // Spring @Enable模块装配
@ConditionalOnSystemProperty(name = "user.name",value = "浴缸") // 条件装装配
public class HelloWorldAutoConfiguration {
}
# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.imooc.diveinspringboot.configuration.HelloWorldAutoConfiguration
HelloWorldAutoConfiguration
1.去掉模式注解: @Configuration,结果运行正常
//@Configuration // Spring 模式注解
@EnableHelloWorld // Spring @Enable模块装配
@ConditionalOnSystemProperty(name = "user.name",value = "浴缸") // 条件装装配
2.修改条件判断
@ConditionalOnSystemProperty(name = "user.name",value = "yk") // 条件装装配
报错: No qualifying bean of type 'com.imooc.diveinspringboot.configuration.HelloWorld' available
3.去掉条件判断,也是运行正常的
//@Configuration // Spring 模式注解
@EnableHelloWorld // Spring @Enable模块装配
//@ConditionalOnSystemProperty(name = "user.name",value = "浴缸") // 条件装装配
spring boot自动装配不是一种新特性,而是依赖于Spring Framework的三种手动装配,可以灵活组合使用
1.减少各种@Enable的配置,避免在代码中使用过多的@Enable
2.通过修改配置文件META-INF/spring.factories来实现插拔式注册,并不需要重新编译