源码地址:github地址
maven依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
|
spring模式注解装配
spring的模式注解可以说是注册一个个单独的组件
Stereotype Annotations
GitHub官方描述
A stereotype annotation is an annotation that is used to declare the role that a component plays within the application. For example, the @Repository
annotation in the Spring Framework is a marker for any class that fulfills the role or stereotype of a repository (also known as Data Access Object or DAO).
@Component
is a generic stereotype for any Spring-managed component. Any component annotated with @Component
is a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with @Component
is also a candidate for component scanning. For example, @Service
is meta-annotated with @Component
.
Core Spring provides several stereotype annotations out of the box, including but not limited to: @Component
, @Service
, @Repository
, @Controller
, @RestController
, and @Configuration
. @Repository
, @Service
, etc. are specializations of @Component
.
模式注解用于声明应用中的组件。例如:@Component
通用组件,任何被@Component
标注的组件都是组件扫描时的候选对象,@Service
扮演服务层的角色,@Controller
扮演控制层的角色,@Repository
扮演仓库存储的角色等注解都是被@Component
标注的,时@Component
派生出来的对象,其作用相同只是可以更加有语义地声明组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {...}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {...}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {...}
|
使用方式:<context:component-scan>方式
在xml配置文件中开启
1
2
3
4
|
<!-- 激活注解驱动特性 -->
<context:annotation-config />
<!-- 找寻被 @Component 或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean -->
<context:component-scan base-package="com.msr.aotuconfiguration" />
|
@ComponentScan
方式通过注解去扫描,他会扫描指定地包及其子包下面的组件
1
2
|
@ComponentScan(basePackages = "com.msr.aotuconfiguration")
public @interface AotuConfigApplication {..}
|
在SpringBoot中的@SpringBootApplication
的启动注解中就已经整合了@ComponentScan
注解
1
2
3
4
5
6
7
8
9
10
|
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {...}
|
@ComponentScan使用Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.msr.aotuconfiguration.repository;
import org.springframework.stereotype.Repository;
@Repository(value = "myUserRepository")
public class UserRepository {
}
@ComponentScan(basePackages = "com.msr.aotuconfiguration.repository")
public class TestRepositoryBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestRepositoryBootstrap.class)
// 非web模式运行
.web(WebApplicationType.NONE)
.run(args);
UserRepository repository = context.getBean(UserRepository.class);
System.out.println("repository bean: " + repository);
//控制台输出:repository bean: com.msr.aotuconfiguration.repository.UserRepository@25084a1e
//说明组件UserRepository组件成功被装配
context.close();
}
}
|
自定义模式注解
通过使用@Component
的“派生性”,可以自定义像@Service
这些注解
自定义@CustomService
注解:@Component
->@Service
->@CustomService
1
2
3
4
5
6
7
|
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface CustomService {
String value() default "";
}
|
使用@CustomService
注解去标注组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@CustomService
public class UserService {
public String get() {
return "custom annotation";
}
}
@ComponentScan(basePackages = "com.msr.aotuconfiguration.service")
public class TestCustomAnnotationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestCustomAnnotationApplication.class)
.web(WebApplicationType.NONE)
.run(args);
UserService userService = context.getBean(UserService.class);
System.out.println("userService bean:"+userService.get());
}
}
|
@CustomService
还可以再次派生成新注解@Component
->@Service
->@CustomService
->…
在自定义组件声明类型注解时,可以再@Component
或者@Service
这些注解之上去自定义。一般都使用@Component
就够了
spring @Enable模块装配
Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立
的单元。比如 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处
理)模块等。
Spring中@Enable
注解模块的一些使用
框架实现 |
@Enable类型注解 |
激活的模块 |
Spring Framework |
@EnableWebMvc |
Web MVC 模块 |
|
@EnableTransactionManagement |
事务管理模块 |
|
@EnableCaching |
缓存模块 |
|
@EnableAsync |
异步处理模块 |
|
@EnableWebFlux |
Web Flux模块 |
|
@EnableAspectJAutoProxy |
AspectJ代理模块 |
SpringBoot |
@EnableAutoConfiguratio |
自动装配模块 |
|
@EnableManagementContext |
Actuator 管理模块 |
|
@EnableConfigurationProperties |
配置属性绑定模块 |
|
@EnableOAuth2Sso |
OAuth2 单点登录模块 |
Spring Cloud |
@EnableEurekaServer |
Eureka服务器模块 |
|
@EnableConfigServer |
配置服务器模块 |
|
@EnableFeignClients |
Feign客户端模块 |
|
@EnableZuulProxy |
服务网关 Zuul 模块 |
|
@EnableCircuitBreaker |
服务熔断模块 |
Spring Security |
@EnableWebSecurity |
Web Seciruty模块 |
|
@EnableGlobalAuthentication |
全局认证注解 |
… |
… |
… |
@Enable
模块装配又有注解驱动方式和接口编程方式
注解驱动方式
Spring中的实现:
1
2
3
4
5
6
7
8
9
10
11
12
|
package org.springframework.web.servlet.config.annotation;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
package org.springframework.web.servlet.config.annotation;
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {...}
|
通过注解驱动方式自定义Enable注解
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
|
package com.msr.aotuconfiguration.annotation;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(UserConfiguration.class)
public @interface EnableUserByConfiguration {
}
package com.msr.aotuconfiguration.configuration;
@Configuration
public class UserConfiguration {
@Bean
public String user(){
return "user configuration";
}
}
package com.msr.aotuconfiguration.bootstrap;
@EnableUserByConfiguration
public class TestEnableBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestEnableBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
String user = context.getBean("user", String.class);
System.out.println("user Bean : " + user);
context.close();
}
}
|
接口编程方式
Spring中的实现:
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
|
package org.springframework.cache.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {...}
package org.springframework.cache.annotation;
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
"org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJCachingConfiguration";
private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJJCacheConfiguration";
private static final boolean jsr107Present = ClassUtils.isPresent(
"javax.cache.Cache", CachingConfigurationSelector.class.getClassLoader());
private static final boolean jcacheImplPresent = ClassUtils.isPresent(
PROXY_JCACHE_CONFIGURATION_CLASS, CachingConfigurationSelector.class.getClassLoader());
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
private String[] getAspectJImports() {
List<String> result = new ArrayList<>(2);
result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
if (jsr107Present && jcacheImplPresent) {
result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
}
return StringUtils.toStringArray(result);
}
}
|
ImportSelector接口是至spring中导入外部配置的核心接口,也是最顶层的接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)都有它的存在
下面就基于接口驱动自定义实现Enable模块:通过UserConfigurationSelector
装配了UserConfiguration
( return new String[]{UserConfiguration.class.getName()} )
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
|
package com.msr.aotuconfiguration.annotation;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(UserConfigurationSelector.class)
public @interface EnableUserByImportSelector {
}
package com.msr.aotuconfiguration.annotation;
public class UserConfigurationSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{UserConfiguration.class.getName()};
}
}
package com.msr.aotuconfiguration.bootstrap;
@EnableUserByImportSelector
public class TestEnableImportSelectorBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestEnableImportSelectorBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
String user = context.getBean("user", String.class);
System.out.println("user Bean : " + user);
context.close();
}
}
|
Spring条件装配
从 Spring Framework 3.1 开始,允许在 Bean 装配时增加前置条件判断
注解 |
使用场景 |
开始版本 |
@Profile |
配置化条件装配 |
3.1 |
@Conditional |
编程条件装配 |
4.0 |
条件装配@Profile—基于配置信息
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
|
package com.msr.aotuconfiguration.service;
@Profile("user1")
@Service
public class User1ServiceByProfile {
public void print(){
System.out.println("profile is user1: "+this.getClass().getName());
}
}
package com.msr.aotuconfiguration.service;
@Profile("user2")
@Service
public class User2ServiceByProfile {
public void print(){
System.out.println("profile is user2: "+this.getClass().getName());
}
}
package com.msr.aotuconfiguration.bootstrap;
@SpringBootApplication(scanBasePackages = "com.msr.aotuconfiguration.service")
public class TestProfileBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestProfileBootstrap.class)
.web(WebApplicationType.NONE)
.profiles("user1")
.run(args);
User1ServiceByProfile user1 = context.getBean("user1ServiceByProfile", User1ServiceByProfile.class);
//成功装配
user1.print();
//直接报错 NoSuchBeanDefinitionException: No bean named 'user2ServiceByProfile' available
User2ServiceByProfile user2 = context.getBean("user2ServiceByProfile",User2ServiceByProfile.class);
if (user2==null){
System.out.println("user2ServiceByProfile can not be load...");
}
}
}
|
条件装配@Conditional—基于编程实现
Spring中的@Conditional
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* The classes that must be present. Since this annotation is parsed by loading class
* bytecode, it is safe to specify classes here that may ultimately not be on the
* classpath, only if this annotation is directly on the affected component and
* <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
* use this annotation as a meta-annotation, only use the {@link #name} attribute.
* @return the classes that must be present
*/
Class<?>[] value() default {};
/**
* The classes names that must be present.
* @return the class names that must be present.
*/
String[] name() default {};
}
|
自定义实现@ConditionOnProperty
:
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
|
package com.msr.aotuconfiguration.condition;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionOnProperty {
/**
* 姓名
* @return 用户姓名
*/
String name();
/**
* 地址
* @return 用户地址
*/
String address();
}
package com.msr.aotuconfiguration.condition;
public class OnPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnProperty.class.getName());
String name = String.valueOf(attributes.get("name"));
String address = String.valueOf(attributes.get("address"));
return "vip".equals(name) && "China".equals(address);
}
}
package com.msr.aotuconfiguration.bootstrap;
public class TestConditionBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConditionBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
String user = context.getBean("maishuren", String.class);
System.out.println(user);
context.close();
}
@Bean("maishuren")
@ConditionOnProperty(name = "vip", address = "China")
public String user() {
return "user bean is name:msr address:China";
}
}
|
SpringBoot自动装配
SpringBoot的自动装配,是基于约定大于配置的原则。实现spring组件自动装配。其中使用了上所述的几种方法:
- Spring模式注解装配
@Enable
模块装配
- Spring条件装配
- Spring工厂加载自动装配
- 实现类:SpringFactoriesLoader
- 读取配置文件:org.springframework.boot.autoconfigure/META-INF/spring-factories
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
83
84
85
86
87
88
|
package org.springframework.core.io.support;
public abstract class SpringFactoriesLoader {
//org.springframework.boot.autoconfigure.EnableAutoConfiguration Enable模块激活自动装配
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
public SpringFactoriesLoader() {
}
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList(factoryNames.size());
Iterator var5 = factoryNames.iterator();
while(var5.hasNext()) {
String factoryName = (String)var5.next();
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
} else {
return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();
}
} catch (Throwable var4) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);
}
}
}
|
例如spring.factories中的org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
|
自定义自动装配
-
启动类激活自动装配:@EnableAutoConfiguration
-
实现自动装配:xxxAutoConfiguration
->UserAutoConfiguration
-
配置自动装配实现:META-INF/spring.factories
-
UserAutoConfiguration实现
-
@ConditionOnProperty(name = "vip", address = "China")
条件装配符合条件再装配
-
@Configuration
将当前类装载进容器,
-
@EnableUserByConfiguration
Enable模块加载
1
2
3
4
5
6
7
|
package com.msr.aotuconfiguration.configuration;
@Configuration
public class UserConfiguration {
@Bean
public String user(){
return "user configuration";
}
|
具体实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.msr.aotuconfiguration.bootstrap;
//启动类
@EnableAutoConfiguration
public class TestAutoConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestAutoConfigurationBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
String user = context.getBean("user", String.class);
System.out.println("user Bean : " + user);
context.close();
}
}
package com.msr.aotuconfiguration.condition;
@Configuration //spring模式注解
@ConditionOnProperty(name = "vip", address = "China") //条件装配
@EnableUserByConfiguration //Enable模块
//spring.factories中
public class UserAutoConfiguration {
}
|
在resources文件夹中创建META-INF文件夹,然后在META-INF中创建spring.factories文件
1
2
3
|
# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.msr.aotuconfiguration.condition.UserAutoConfiguration
|
未完!待完善…
文章有什么错误或者有什么问题,欢迎大家通过通论提交issue给我吧!