2.走向自动装配
Spring 模式注解装配
2-1 走向自动装配
-
课程介绍java
- spring framework手动装配
- spring boot自动装配
- spring boot自动装配是以spring framework手动装配为基础实现的
2-2 Spring Framework 手动装配
[模式注解(Stereotype Annotations)]()
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
.web
- 模式注解是一种用于声明在应用中扮演“组件”角色的注解。如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的模式注解。
- @Component 做为一种由 Spring 容器托管的通用模式组件,任何被 @Component 标注的组件均为组件扫描的候选对象。相似地,凡是被 @Component 元标注(meta-annotated)的注解,也即被@Component 注解标注过的注解,如 @Service ,且这两个注解的签名也一致时,当任何组件标注它时,也被视做组件扫描的候选对象 。
模式注解举例
Spring Framework 注解 |
场景说明 |
起始版本 |
@Repository |
数据仓储模式注解 |
2.0 |
@Component |
通用组件模式注解 |
2.5 |
@Service |
服务模式注解 |
2.5 |
@Controller |
Web 控制器模式注解 |
2.5 |
@Configuration |
配置类模式注解 |
3.0 |
装配方式
<context:component-scan>
方式
<?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/springcontext.xsd">
<!-- 激活注解驱动特性 -->
<context:annotation-config />
<!-- 找寻被 @Component 或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean -->
<context:component-scan base-package="com.imooc.dive.in.spring.boot" />
</beans>
@ComponentScan 方式
@ComponentScan(basePackages = "com.imooc.dive.in.spring.boot")
public class SpringConfiguration {
...
}
2-3 Spring Framework手动装配自定义模式注解
自定义模式注解
- @firstLevelRepository注解“继承”了@repository,同时@repository注解又“继承”了@component,三个注解在注解关系上层层“继承”,同时注解的签名字段即注解属性方法也一致,只有一个value字段,用于指定bean的name。同理,@SecondLevelRepository注解被@FirstLevelRepository注解标注也会有一样的效果,体现了层次性。 “派生性”侧重于从spring已有的注解生成新的注解,“层次性”侧重于这些注解之间所谓的层层”继承“关系。
@Component
“派生性”
package com.imooc.springbootautoconfigure.annotation;
import org.springframework.stereotype.Repository;
import java.lang.annotation.*;
/**
* 一级 {@link Repository @Repository}
*
* @author gaorj
* @since 2018/10/16
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repository
public @interface FirstLevelRepository {
String value() default "";
}
@Component
“层次性”
- @SecondLevelRepository注解定义
package com.imooc.springbootautoconfigure.annotation;
import java.lang.annotation.*;
/**
* @author gaorj
* @since 2018/10/16
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@FirstLevelRepository
public @interface SecondLevelRepository {
String value() default "";
}
package com.imooc.springbootautoconfigure.repository;
import com.imooc.springbootautoconfigure.annotation.FirstLevelRepository;
import com.imooc.springbootautoconfigure.annotation.SecondLevelRepository;
import org.springframework.stereotype.Component;
/**
*
* @author gaorj
* @since 2018/10/16
*/
//@FirstLevelRepository(value = "myFirstLevelRepository")//value指定bean的名称
//@Component(value = "myFirstLevelRepository")//一样的效果,注解的派生性
@SecondLevelRepository(value = "myFirstLevelRepository")//一样的效果,注解的层次性
public class MyFirstLevelRepository {
}
- Repository启动类的使用,用于验证自定义注解的使用。此处没有使用默认的@SpringBootApplication注解,使用SpringApplicationBuilder初始化上下文
package com.imooc.springbootautoconfigure.bootstrap;
import com.imooc.springbootautoconfigure.repository.MyFirstLevelRepository;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
/**
* Repository启动类
* @author gaorj
* @since 2018/10/16
*/
//扫描bean目录
@ComponentScan(value ="com.imooc.springbootautoconfigure.repository")
public class RepositoryBootstrap {
public static void main(String[] args) {
//springboot上下文初始化
ConfigurableApplicationContext context = new SpringApplicationBuilder(RepositoryBootstrap.class)
//非web工程
.web(WebApplicationType.NONE)
.run(args);
//myFirstLevelRepository bean是否存在
MyFirstLevelRepository myFirstLevelRepository = context.getBean("myFirstLevelRepository", MyFirstLevelRepository.class);
System.out.println("myFirstLevelRepository"+myFirstLevelRepository);
//关闭上下文
context.close();
}
}
Spring @Enable 模块装配
- Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具有相同领域的功能组件集合, 组合所造成一个独立的单元。所谓模块是为了实现一个功能所涉及到的各个组件的集合。好比 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处理)模块等。
2-4 @Enable 模块装配两种方式
@Enable
注解模块举例
框架实现 |
@Enable 注解模块 |
激活模块 |
Spring Framework |
@EnableWebMvc |
Web MVC 模块 |
|
@EnableTransactionManagement |
事务管理模块 |
|
@EnableCaching |
Caching 模块 |
|
@EnableMBeanExport |
JMX 模块 |
|
@EnableAsync |
异步处理模块 |
|
EnableWebFlux |
Web Flux 模块 |
|
@EnableAspectJAutoProxy |
AspectJ 代理模块 |
Spring Boot |
@EnableAutoConfiguration |
自动装配模块 |
|
@EnableManagementContext |
Actuator 管理模块 |
|
@EnableConfigurationProperties |
配置属性绑定模块 |
|
@EnableOAuth2Sso |
OAuth2 单点登陆模块 |
Spring Cloud |
@EnableEurekaServer |
Eureka服务器模块 |
|
@EnableConfigServer |
配置服务器模块 |
|
@EnableFeignClients |
Feign客户端模块 |
|
@EnableZuulProxy |
服务网关 Zuul 模块 |
|
@EnableCircuitBreaker |
服务熔断模块 |
实现方式
注解驱动方式
- 经过引入@Import一个被@configuration模式注解的类来实现此模块的功能,加载相应的bean。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration
public class DelegatingWebMvcConfiguration extends
WebMvcConfigurationSupport {
...
}
接口编程方式
- 引入@Import的类必须实现ImportSelector接口,此接口可以返回多个配置bean,根据注解配置的签名信息决定引入bean,从而实现模块装配的多样化。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
...
}
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
/**
* {@inheritDoc}
* @return {@link ProxyCachingConfiguration} or {@code
AspectJCacheConfiguration} for
* {@code PROXY} and {@code ASPECTJ} values of {@link
EnableCaching#mode()}, respectively
*/
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;
}
}
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotationMetadata;
/**
* Interface to be implemented by types that determine which @{@link Configuration}
* class(es) should be imported based on a given selection criteria, usually one or more
* annotation attributes.
*
* <p>An {@link ImportSelector} may implement any of the following
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #selectImports}:
* <ul>
* <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
* </ul>
*
* <p>ImportSelectors are usually processed in the same way as regular {@code @Import}
* annotations, however, it is also possible to defer selection of imports until all
* {@code @Configuration} classes have been processed (see {@link DeferredImportSelector}
* for details).
*
* @author Chris Beams
* @since 3.1
* @see DeferredImportSelector
* @see Import
* @see ImportBeanDefinitionRegistrar
* @see Configuration
*/
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
自定义 @Enable
模块
基于注解驱动实现 - @EnableHelloWorld
-
HelloWorldConfiguration
-> HelloWorld
- 自定义@EnableHelloWorld注解
package com.imooc.springbootautoconfigure.annotation;
import com.imooc.springbootautoconfigure.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author gaorj
* @since 2018/10/16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//@Import(HelloWorldConfiguration.class)//注解驱动实现方式
@Import(HelloWorldImportSelector.class)//接口编程实现方式
public @interface EnableHelloWorld {
}
- 基于@Configuration注解import引入的配置类
package com.imooc.springbootautoconfigure.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author gaorj
* @since 2018/10/16
*/
@Configuration
public class HelloWorldConfiguration {
@Bean
public String helloWorld() { // 方法名即 Bean 名称
return "Hello,World 2018";
}
}
基于接口驱动实现 - @EnableServer
-
HelloWorldImportSelector
-> HelloWorldConfiguration
-> HelloWorld
- 基于实现ImportSelector接口import引入的配置类
package com.imooc.springbootautoconfigure.annotation;
import com.imooc.springbootautoconfigure.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author gaorj
* @since 2018/10/16
*/
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
package com.imooc.springbootautoconfigure.bootstrap;
import com.imooc.springbootautoconfigure.annotation.EnableHelloWorld;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @author gaorj
* @since 2018/10/16
*/
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// helloWorld Bean 是否存在
String helloWorld =
context.getBean("helloWorld", String.class);
System.out.println("helloWorld Bean : " + helloWorld);
// 关闭上下文
context.close();
}
}
- 两种实现方式对比,接口驱动实现因为中间通过ImportSelector转换,此种方式相比注解驱动实现更有弹性,可以实现bean加载方式的多样化,自定义实现多种返回值,相似于条件装配。
Spring 条件装配
2-5 Spring条件装配
- 从 Spring Framework 3.1 开始,容许在 Bean 装配时增长前置条件判断
条件注解举例
Spring 注解 |
场景说明 |
起始版本 |
@Profile |
配置化条件装配 |
3.1 |
@Conditional |
编程条件装配 |
4.0 |
- profile参数配置方式有局限,conditional自定义编程更大的灵活性
实现方式
配置方式 - @Profile
- @profile注解其实是经过@conditional注解实现的
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
编程方式 - @Conditional
- @Conditional 注解的定义及其使用,必须配合Condition接口使用。
- 判断@Conditional注解的签名类OnClassCondition.class是否在当前classpath下存在。
- OnClassCondition.class这个类必须实现Condition接口,在此类中根据 @ConditionalOnClass注解的签名信息做匹配判断,是否装配此bean。
/**
* {@link Conditional} that only matches when the specified classes are on the classpath.
*
* @author Phillip Webb
*/
@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 {};
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition}s that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked.
* @return {@code true} if the condition matches and the component can be registered
* or {@code false} to veto registration.
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
2-6 基于配置方式实现自定义条件装配
自定义条件装配
基于配置方式实现 - @Profile
- 计算服务,多整数求和 :sum
- @Profile("Java7") : for 循环
- @Profile("Java8") : Lambda
- CalculateService求和接口
package com.imooc.springbootautoconfigure.service;
/**
* @author gaorj
* @since 2018/10/16
*/
public interface CalculateService {
/**
* 从多个整数 sum 求和
* @param values 多个整数
* @return sum 累加值
*/
Integer sum(Integer... values);
}
- CalculateService求和接口java7实现
package com.imooc.springbootautoconfigure.service;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
/**
* @author gaorj
* @since 2018/10/16
*/
@Service
@Profile("Java7")
public class Java7CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("Java 7 for 循环实现 ");
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
public static void main(String[] args) {
CalculateService calculateService = new Java7CalculateService();
System.out.println(calculateService.sum(1,2,3,4,5,6,7,8,9,10));
}
}
- CalculateService求和接口java8实现
package com.imooc.springbootautoconfigure.service;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import java.util.stream.Stream;
/**
* @author gaorj
* @since 2018/10/16
*/
@Service
@Profile("Java8")//条件配置装配
public class Java8CalculateService implements CalculateService{
@Override
public Integer sum(Integer... values) {
System.out.println("Java 8 Lambda 实现");
//Integer sum = Stream.of(values).reduce(0, (integer1, integer2) -> integer1 + integer2);
int sum = Stream.of(values).reduce(0, Integer::sum);
return sum;
}
public static void main(String[] args) {
CalculateService calculateService = new Java8CalculateService();
System.out.println(calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
}
}
package com.imooc.springbootautoconfigure.bootstrap;
import com.imooc.springbootautoconfigure.service.CalculateService;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @author gaorj
* @since 2018/10/16
*/
@SpringBootApplication(scanBasePackages ="com.imooc.springbootautoconfigure.service")
public class CalculateServiceBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateServiceBootstrap.class).web(WebApplicationType.NONE)
.profiles("Java7")
.run(args);
// CalculateService Bean 是否存在
CalculateService calculateService = context.getBean(CalculateService.class);
System.out.println("calculateService.sum(1...10) : " +
calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// 关闭上下文
context.close();
}
}
2-7 基于编程方式实现条件装配
基于编程方式实现 - @ConditionalOnSystemProperty