Featured image of post Spring Boot系列一:浅析SpringBoot自动装配

Spring Boot系列一:浅析SpringBoot自动装配

从spring的手动装配走向springboot自动装配。(Spring模式注解、@Enable模块注解、spring条件注解)-->SpringBoot自动装配

源码地址: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(功能性注解)都有它的存在

importselector

​ 下面就基于接口驱动自定义实现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给我吧!