Spring Boot2.0深度实践之核心技术篇 – 第2章 走向自动装配
第2章 走向自动装配
Spring Framework 手动装配
1. Spring 模式注解装配
模式注解是一种用于声明在应用中扮演“组件
”角色的注解。如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的模式注解。
@Component 作为一种由 Spring 容器
托管的通用模式组件,任何被 @Component 标准的组件均为组件扫描的候选对象
。类 似地,凡是被 @Component 元标注(meta-annotated)的注解,如 @Service ,当任何组件标注它时,也被视作组件扫 描的候选对象
- @Component
/**
* @author Mark Fisher
* @since 2.5
* @see Repository
* @see Service
* @see Controller
* @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
- @Service
/**
* @author Juergen Hoeller
* @since 2.5
* @see Component
* @see Repository
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
- @Configuration
* @author Rod Johnson
* @author Chris Beams
* @since 3.0
* @see Bean
* @see Profile
* @see Import
* @see ImportResource
* @see ComponentScan
* @see Lazy
* @see PropertySource
* @see AnnotationConfigApplicationContext
* @see ConfigurationClassPostProcessor
* @see org.springframework.core.env.Environment
* @see org.springframework.test.context.ContextConfiguration
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
}
装配方式
- 第一种方式
<context:component-scan>
,(since 2.5)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 激活注解驱动特性-->
<context:annotation-config/>
<!-- 开启扫描注解 -->
<context:component-scan base-package="meng.spring.test8"></context:component-scan>
</beans>
- 第二种方式 @ComponentScan ,(since 3.1)
// BeanAnnotation.java
package meng.spring.test8.annotation;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope(value="prototype")//默认为单例 singleton 可以改为prototype
@Component("beanAnnotation")//默认就是beanAnnotation
public class BeanAnnotation {
public void say(String arg) {System.out.println("BeanAnnotation : " + arg);}
}
// AnnotationConfig.java
@ComponentScan(basePackages = "meng.spring.test8.annotation")
public class AnnotationConfig {
}
// Test2.java
public class Test2 {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class);
BeanAnnotation beanAnnotation = context.getBean("beanAnnotation",
BeanAnnotation.class);
beanAnnotation.say("Hello 注解bean");
System.out.println(beanAnnotation.hashCode());
BeanAnnotation beanAnnotation2 = context.getBean("beanAnnotation",
BeanAnnotation.class);
beanAnnotation2.say("Hello 注解bean");
System.out.println(beanAnnotation2.hashCode());
}
}
2. @Enable 模块装配 – 自定义 @Enable 模块
- @EnableHelloWorld (基于注解驱动实现)
import com.example.applicationdemo.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
@Configuration
public class HelloWorldConfiguration {
@Bean
public String helloWorld() {return "Hello World";}
}
// HelloWorldBootStrap.java
@EnableHelloWorld
public class HelloWorldBootStrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(HelloWorldBootStrap.class).web(WebApplicationType.NONE).run(args);
String bean = applicationContext.getBean("helloWorld", String.class);
System.out.println("----> " + bean);
}
}
- @EnableCaching (基于接口驱动实现)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld2 {
}
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
// HelloWorldConfiguration.java 同上
// HelloWorldBootStrap.java
@EnableHelloWorld2
public class HelloWorldBootStrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(HelloWorldBootStrap.class).web(WebApplicationType.NONE).run(args);
String bean = applicationContext.getBean("helloWorld", String.class);
System.out.println("----> " + bean);
}
}
3. @Enable 模块装配 – Spring 中的 @Enable 模块
Spring Framework 3.1 开始支持“@Enable 模块驱动
”。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立 的单元。比如 Web MVC 模块、AspectJ 代理模块、Caching (缓存)模块、JMX(Java 管理扩展)模块、Async (异步处理)模块等。
- 注解驱动方式
@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<EnableCaching> {
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] { AutoProxyRegistrar.class.getName() , ProxyCachingConfiguration.class.getName() };
case ASPECTJ:
return new String[] { AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME };
default:
return null;
}
}
}
Spring Framework 条件装配
从 Spring Framework 3.1 开始,允许在 Bean 装配时增加前置条件判断
- 配置化条件装配 @Profile
since 3.1
public interface AddressService {
String getMainCity();
}
@Profile("Anhui")
@Service
public class AnhuiService implements AddressService {
@Override
public String getMainCity() {return "HeFei";}
}
@Profile("Zejiang")
@Service
public class ZejiangService implements AddressService {
@Override
public String getMainCity() {return "HangZhou";}
}
// AddressBootstrap.java
@SpringBootApplication(scanBasePackages = "com.example.applicationdemo.service")
public class AddressBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(AddressBootstrap.class)
.web(WebApplicationType.NONE)
.profiles("Zejiang")
.run(args);
AddressService bean = applicationContext.getBean(AddressService.class);
System.out.println("----> " + bean.getMainCity());
}
}
- 编程条件装配 @Conditional
since 4.0
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
String name();
String value();
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
import java.util.Objects;
public class OnSystemPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
String name = String.valueOf(map.get("name"));
String value = String.valueOf(map.get("value"));
String systemValue = System.getProperty(name);
return Objects.equals(value, systemValue);
}
}
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
public class OnSystemPropertyConditionBootStrap {
@Bean
@ConditionalOnSystemProperty(name = "user.name", value = "kevin")
public String helloWorld() {
return "Hello OnSystemPropertyConditionBootStrap";
}
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(OnSystemPropertyConditionBootStrap.class).web(WebApplicationType.NONE).run(args);
String bean = applicationContext.getBean("helloWorld", String.class);
System.out.println("----> " + bean);
}
}
SpringBoot 自动装配
在 Spring Boot 场景下,基于约定大于配置
的原则,实现 Spring 组件自动装配的目的
1. 底层装配技术
- Spring 模式注解装配
- Spring @Enable 模块装配
- Spring 条件装配装配
- Spring 工厂加载机制
- 实现类: SpringFactoriesLoader
- 配置资源:
META-INF/spring.factories
SpringFactoriesLoader.java
package org.springframework.core.io.support;
/**
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2
*/
public final class SpringFactoriesLoader {
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<>();
private SpringFactoriesLoader() {
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
// ...
}
2. 实现三部曲
- 激活自动装配
@EnableAutoConfiguration
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
@EnableAutoConfiguration
public class EnableAutoConfigurationBootStrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(EnableAutoConfigurationBootStrap.class)
.web(WebApplicationType.NONE)
.run(args);
String bean = applicationContext.getBean("helloWorld", String.class);
System.out.println("----> " + bean);
applicationContext.close();
}
}
- 实现自动装配
XxxAutoConfiguration
package com.example.applicationdemo.auto;
import com.example.applicationdemo.annotation.EnableHelloWorld;
import com.example.applicationdemo.condition.ConditionalOnSystemProperty;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableHelloWorld
@ConditionalOnSystemProperty(name = "user.name", value = "kevin")
public class HelloWorldAutoConfiguration {
}
- 配置自动装配实现
META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.applicationdemo.auto.HelloWorldAutoConfiguration
https://coding.imooc.com/lesson/252.html#mid=16193