学习springboot

手动装配

spring模式注解装配

  • 定义:一种用于生命在应用中扮演”组件”角色的注解
  • 举例:@Component,@Service,@Configuration等
  • 装配: <context:component-scan>或者@ComponentScan

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 .

模式注解是一种用于声明在应用中扮演”组件”角色的注解。如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的模式注解。
@Component 作为一种由 Spring 容器托管的通用模式组件,任何被 @Component 标准的组件均为组件扫描的候选对象。类似地,凡是被 @Component 元标注(meta-annotated)的注解,如 @Service ,当任何组件标注它时,也被视作组件扫描的候选对象

Spring Framework 注解 场景说明 起始版本
@Repository 数据仓储模式注解 2.0
@Component 通用组件模式注解 2.5
@Service 服务模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置类模式注解(代替配置文件进行描述) 3.0

装配方式

  • <context:component-scan>方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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 />
    <!-- 找寻被 @Component 或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean -->
    <context:component-scan base-package="cn.xwmdream.dive.in.spring.boot" />
    </beans>
  • @ComponentScan 方式

    1
    2
    3
    4
    @ComponentScan(basePackages = "cn.xwmdream.dive.in.spring.boot")
    public class SpringConfiguration {
    ...
    }

自定义模式注解

  • @Component “派生性”
    1
    2
    3
    4
    5
    6
    7
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Service
    public @interface FirstLevelService {
    String value() default "";
    }

派生性指的是@Component->@Service->@FirstLevelService
派生性不是真的存在,类似class的继承

  • @Component “层次性”
    1
    2
    3
    4
    5
    6
    7
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @FirstLevelService
    public @interface SecondLevelService {
    String value() default "";
    }

@Component->@Service->@FirstLevelService->@SecondLevelService

Spring @Enable 模块装配

  • 具备相同的功能组件集合,组成所形成一个独立的单元
  • 距离:@EnableWebMvc,@EnableAutoConfiguration等
  • 实现:注解方式,编程方式

Spring Framework 3.1 开始支持”@Enable 模块驱动”。所谓”模块”是指具备相同领域的功能组件集合, 组合所形成一个独立的单元。比如 Web MVC模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处理)模块等。

@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 服务熔断模块

实现方式

注解驱动方式
1
2
3
4
5
6
7
@Configuration
public class HelloWorldConfiguration {
@Bean
public String hello(){
return "Hello world 中国";
}
}
1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
1
2
3
4
5
6
7
8
9
10
11
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext run = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class).web(WebApplicationType.NONE)
.run(args);
String ans = run.getBean("hello",String.class);
System.out.println(ans);
//关闭上下文
run.close();
}
}

EnableHelloWorld注解上import了HelloWorldConfiguration类,所以运行EnableHelloWorldBootstrap就可以获取到HelloWorldConfiguration类下的hello的bean能被装配到容器中

接口编程方式
1
2
3
4
5
6
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//@Import(HelloWorldConfiguration.class)
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}

EnableHelloWorld的import改成了HelloWorldImportSelector,而HelloWorldImportSelector的selectImports返回了HelloWorldConfiguration,所以可以将HelloWorldConfiguration下的bean装配到容器中。注意HelloWorldImportSelector的方法可以返回多个配置类

自定义条件装配

基于配置方式实现 -@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
public interface CalculateService {
Integer sum(Integer... integers);
}

@Service
@Profile("Java8")
public class Java8CalculateService implements CalculateService {
@Override
public Integer sum(Integer... integers) {
System.out.println("java8");
int sum = Stream.of(integers).reduce(0,Integer::sum);
return sum;
}
}

@Service
@Profile("Java7")
public class Java7CalculateService implements CalculateService {
@Override
public Integer sum(Integer... integers) {
System.out.println("java7");
int sum = 0;
for(int i= 0 ;i<integers.length;i++){
sum+=integers[i];
}
return sum;
}
}
  • 两个类都实现了CalculateService接口的service,但是@profile不同,调用的时候根据这个参数可以加载不同的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication(scanBasePackages = "com.example.studyspringboot.service")
public class CalculateServiceBootStrap {
public static void main(String[] args) {
ConfigurableApplicationContext run = new SpringApplicationBuilder(CalculateServiceBootStrap.class).web(WebApplicationType.NONE)
.profiles("Java8")
.run(args);
CalculateService calculateService = run.getBean(CalculateService.class);
System.out.println("1+...+10:"
+calculateService.sum(1,2,3,4,5,6,7,8,9,10));
//关闭上下文
run.close();
}
}
  • 可以看到加载上下文的时候指定profiles参数,不同的参数对应不同实现类上@Profile的参数

基于编程方式实现(Condition)

自定义Condition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 系统属性条件判断
*/
public class OnSystemPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取到ConditionalOnSystemProperty注解的所有的参数
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());

//分别获取到name和value属性
String propertyName = String.valueOf(attributes.get("name"));
String propertyValue = String.valueOf(attributes.get("value"));
//获取系统的name属性值
String javaPropertyValue = System.getProperty(propertyName);
//如果相同返回true
return propertyValue.equals(javaPropertyValue);
}
}

  • 自定义注解

    1
    2
    3
    4
    5
    6
    7
    8
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Documented
    @Conditional(OnSystemPropertyCondition.class)
    public @interface ConditionalOnSystemProperty {
    String name();
    String value();
    }
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //我使用这台电脑的user.name是hp
    @Bean
    @ConditionalOnSystemProperty(value="hp",name="user.name")
    public String helloWorld(){
    return "hello 2019";
    }
    public static void main(String[] args) {
    ConfigurableApplicationContext run = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootStrap.class).web(WebApplicationType.NONE)
    .run(args);
    String h = run.getBean("helloWorld",String.class);
    System.out.println(h);
    //关闭上下文
    run.close();
    }
  • 在注解上使用Conditional指定一个Condition,当调用helloWorld的bean时会激活它头上的ConditionalOnSystemProperty注解,这个注解会调用OnSystemPropertyCondition的matches方法,如果true才会加载这个bean

  • 我这台电脑的user.name是hp,所以在matches最后返回的值是true

自动装配

实现方式

  1. 激活自动装配-@EnableAutoConfiguration
  2. 实现自动装配-xxxAutoConfiguration
  3. 配置自动装配实现-META-INF/spring.factories

自定义自动装配

  1. 创建自动装配类
    1
    2
    3
    4
    @Configuration// spring模式注解
    @EnableHelloWorld//spring@Enable模块装配
    @ConditionalOnSystemProperty(name="user.name",value="hp")//条件装配
    public class HelloWorldAutoConfiguration {}
  • 在这个类上调用了@Enablehelloworld,使用之前的模块装配将’hello’bean装配进来
  • 使用了ConditionalOnSystemProperty进行条件装配

‘HelloWorldAutoConfiguration’
条件判断: user.name == “hp”
模式注解: @Configuration
@Enable 模块: @EnableHelloWorld -> HelloWorldImportSelector -> HelloWorldConfiguration - > hello bean

  1. 创建(resources)/META-INF/spring.factories,指定自动装配类
    1
    2
    3
    #自动装配
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.example.studyspringboot.configuration.HelloWorldAutoConfiguration
  • 会将HelloWorldAutoConfiguration装配到容器中
  1. 运行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @EnableAutoConfiguration
    public class EnableAutoConfigurationBootstrap {
    public static void main(String[] args) {
    ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class).web(WebApplicationType.NONE)
    .run(args);
    String hello = context.getBean("hello", String.class);
    System.out.println(hello);
    //关闭上下文
    context.close();
    }
    }
  • 注解EnableAutoConfiguration会将spring.factories的HelloWorldAutoConfiguration类自动装配进来,然后装配@Enablehelloworld所指定的类

  • 只是在springframework手动装配基础上,增加了spring.factories和EnableAutoConfiguration,实现了自动装配

理解SpringApplication

  • 定义:Spring应用引导类,提供便利的自定义行为方法
  • 场景:嵌入式Web应用和非Web应用
  • 运行:SpringApplication#run(String …)

SpringApplication基本使用

SpringApplication运行

1
SpringApplication.run(xxx.class,args);
  • 将启动Java的参数args传给了SpringApplication

自定义SpringApplication

SpringApplication准备阶段

  • 配置:SpringBean源
  • 推断:Web应用类型和主引导类(Main Class)
  • 加载:应用上下文初始器和应用事件监听器

配置SpringBean源