Spring注解指南

获取容器

  • 通过配置文件获取:new ApplicationContext(“bean.xml”);
  • 通过配置类获取:new AnnotationConfigApplicationContext(xxConfig.class);

获取系统运行环境

1
2
3
4
//获取环境
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//获取操作系统的名字
String os = environment.getProperty("os.name");

给容器中添加组件方式

11.JPG

  1. 包扫描+注解(有局限性:只用于自己写的类,从外部导入的不能用这种方法)

    1
    2
    <component:contxt-scan="com.xxx"/>
    @Controller、@Service、@Repository、@Component
  2. @Configuration+@Bean(可以导入第三方包里面的组件)

  3. @Import(快速给容器中导入一个组件)

  4. 使用Spring提供的FactoryBean(工厂Bean)

  • 自己写一个工厂类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class ColorFactoryBean implements FactoryBean<Color> {

    //返回一个Color对象,这个对象会添加到容器中
    @Override
    public Color getObject() throws Exception {
    return new Color();
    }

    //返回对象的类型
    @Override
    public Class<?> getObjectType() {
    return Color.class;
    }

    //控制是否单例
    // true表示单实例,容器中只创建一次bean,即只调用一次getObject方法
    // false表示多实例,每一次都要调用getObject方法创建一个bean
    @Override
    public boolean isSingleton() {
    return false;
    }
    }
  • 将这个工厂类添加到容器中

    1
    2
    3
    4
    @Bean
    public ColorFactoryBean colorFactoryBean(){
    return new ColorFactoryBean();
    }

BeanPostProcessor–Bean后置处理器

xxxAware

在自定义的组件中要使用Spring底层的一些组件(ApplicationContext、BeanFactory等),就需要实现形式为xxxAware的接口,在创建对象的时候,会调用接口规定的方法来注入相关组件。

xxxAware就是使用xxxProcessor(后置处理器来完成相关的功能),每一个xxxAware就对应一个xxxProcessor,例如:

1
ApplicationContextAware --> ApplicationContextAwareProcessor

@Configuration

标注在一个类上,表示这是一个配置类,这时这个配置类就相当于之前的xml配置文件

@Bean

一般在 @Configuration 标注的类里面使用,标注在一个方法上,相当于xml配置文件中的,表示将一个方法的返回类型添加到容器中。
01.JPG
如果@Bean里面不加参数的话,就默认用方法名作为组件的id,加了参数就以参数为组件的id。

@Bean标注的方法来创建容器的时候,方法参数的值默认是从容器里面获取的,效果和加上@Autowired注解一样,例如:

1
2
3
4
5
6
7
@Bean
public Color color(Car car){
Color color = new Color();
color.setCar(car);
return color;
}
//这里的参数就是从容器里面获取的

@ComponentScan

02.JPG
这个注解就相当于在xml配置文件中的

1
<component:contxt-scan="com.xxx"/>

它表示将以 @Controller@Service@Repository@Component注解的类当成组件扫描到容器中。在java8过后,可以在同一个配置类上添加多个。

  • value: 表示扫描范围

  • excludeFilters: 表示扫描的时候排除哪些组件,它是Filter数组形式,因此里面的参数用大括号括起来

    1
    2
    3
    @Filter(type=FilterType.ANNOTATION,classes={Controller.class})
    type表示排除的规则,这里是排除注解规则
    classes表示要排除哪些,它是数组形式,参数是根据类型的
  • includeFilters: 指定扫描的时候包含哪些组件,具体的参数和之前的excludeFilters一致,需要区别一点的是,需要加上useDefaultFilters = false来禁用默认的拦截器。

  • FilterType

    1
    2
    3
    4
    5
    FilterType.ANNOTATION:按照注解,这时classes里面的参数可以是Controller.class、Service.class、Component.class这些注解
    FilterType.ASSIGNABLE_TYPE:按照类型,这时classes可以是在在容器中一个自定义类型,比如 BookService.class
    FilterType.ASPECTJ:按照AspectJ表达式(不常用)
    FilterType.REGEX:按照正则表达式(不常用)
    FilterType.CUSTOM:按照自定义规则,这时classes里面可以为 MyFilterType.class,其中MyFilterType这个类必须实现TypeFilter,然后实现match方法。

@ComponentScans

03.JPG
它用于多个ComponentScan的扫描,其中value里面为数组形式,可以指定多个 @ComponentScan

@Scope

04.JPG

定义作用范围,可以用在方法或者类上,取值如下:

  • singleton:单实例(默认为这个),ioc容器在启动的时候(new AnnotationConfigApplicationContext(xxConfig.class)的时候)会调用方法创建对象放到IOC容器中,后面每次获取就是直接中容器中拿,而不是再来创建。
  • prototype:多实例(每次创建的对象不同),IOC容器启动的时候并不会调用方法创建对象放到容器中,而是在获取的时候才会调用方法创建对象(即在applicationContext.getBean(“persion”)的时候),而且每次都创建对象。
  • request:同一次请求创建一个实例
  • session:同一个session创建一个实例

@Lazy

懒加载,用于单实例的时候,上面有讲在单实例的时候会在容器启动(new AnnotationConfigApplicationContext(xxConfig.class))的时候创建对象,而使用了懒加载后,容器启动时不创建对象,在第一次使用(获取)(applicationContext.getBean(“xx”))的时候才创建对象。
05.JPG

@Conditional(重点)

按照一定的条件进行判断,满足条件的才给容器中添加组件。它可以标注在类和方法上。
06.JPG
他的参数为数组形式,其参数必须实现Condition接口,实现其matchs方法,例如:WindowsCondition就是自己写的一个类,它实现了Condition接口。如下图:
07.JPG

@Import(重点)

第一种方式

给容器中导入组件,例如将Color组件添加到容器中,其id默认是该组件的全类名(带包名)。
08.JPG
其参数是数组形式,因此可以添加大括号导入多个组件,例如:

1
@Import({Color.class,Red.class})

第二种方式(用得比较多)

  • 实现ImportSelector接口,返回需要导入组件的全类名数组
    09.JPG
  • 在@Import注解里面添加上这个自定义的选择器,例:
    1
    @Import({MyImportSelector.class})

第三种方式

  • 实现ImportBeanDefinitionRegistrar接口,手动注册bean到容器中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
    //判断容器中是否包含某个组件
    boolean definition = beanDefinitionRegistry.containsBeanDefinition("com.cdn.Color");
    if (definition){
    //指定Bean的类型
    RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
    //注册一个Bean,指定bean的id
    beanDefinitionRegistry.registerBeanDefinition("rainBow",beanDefinition);
    }
    }
    }
  • 在@Import注解里面添加上自定义注册

    1
    @Import({MyImportBeanDefinitionRegistrar.class})

@Bean生命周期

bean创建–bean初始化–bean销毁

默认是容器来管理bean的生命周期,我们可以自定义bean的初始化和销毁方法,容器在bean进行到当前生命周期的时候来调用自定义的相应的方法。

创建容器 –> 调用初始化方法 –> 关闭容器 –> 调用销毁方法

单实例

  • 初始化时机:对象创建完成,并赋值好,调用初始化方法
  • 销毁时机:容器调用close方法关闭后,调用销毁方法

多实例

  • 初始化:在获取容器的时候,才调用初始化方法
  • 销毁:多实例中容器不会管理bean的销毁

指定初始化和销毁方法

12.JPG
第一种方式

  1. 指定初始化和销毁方法
  2. 在@Bean注解中添加初始化和销毁方法
    1
    2
    3
    4
    5
    //其中的init和destroy方法是Car类里面定义好的方法
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Car car(){
    return new Car();
    }

第二种方式

Bean实现InitializingBean接口实现初始化逻辑;实现DisposableBean接口定义销毁逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Cat implements InitializingBean, DisposableBean {
//销毁方法
@Override
public void destroy() throws Exception {

}

//初始化方法
@Override
public void afterPropertiesSet() throws Exception {

}
}

然后将Cat添加到容器中即可

1
2
3
4
@Bean
public Cat cat(){
return new Cat();
}

第三种方式

使用JSR250规范里面的注解

  • @PostConstruct:在对象创建并赋值好之后调用
  • @PreDestroy:容器关闭后调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Cat {
    public Cat(){

    }
    //在对象创建并赋值好之后调用
    @PostConstruct
    public void init(){

    }
    //容器关闭后调用
    @PreDestroy
    public void destroy(){

    }
    }

然后再将Cat类添加到容器中

1
2
3
4
@Bean
public Cat cat(){
return new Cat();
}

第四种方式

BeanPostProcessor:Bean的后置处理器,是一个接口,在bean初始化前后进行一些处理工作,即使我们没有定义初始化和销毁方法,这个也会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyBeanPostProcessor implements BeanPostProcessor {

//初始化之前调用
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//初始化之后调用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

然后将MyBeanPostProcessor添加到容器中

1
2
3
4
@Bean
public MyBeanPostProcessor myBeanPostProcessor(){
return new MyBeanPostProcessor();
}

@PropertySource

加载外部配置文件,保存到运行环境中,其中的value是数组形式,可以导入多个配置文件。它是可重复标注的注解,可以在一个类上同时标注多个。

1
2
3
4
5
@PropertySource(value={"classpath:person.properties"})
@Congiguration
public class MainConfig{

}

@Value

对组件中的属性赋值,可以用在参数上。

  1. 基本类型

    1
    2
    @Value("张三")
    private String name;
  2. SpEL,Spring表达式

    1
    2
    @Value("#{20-2}")
    private Integer age;
  3. ${},取出配置文件中的值(运行在环境变量里面的值)

    1
    2
    3
    //person.name是配置文件中指定的值
    @Value("${person.name}")
    private String name;

自动注入

自动注入,在一个组件中需要另外一个组件,就用注解将组件注入进来。

@Autowired

Spring里面的规范,可以标注在构造器、方法、属性、参数上。

  • 标注在方法上(一般在setter方法上)。Spring容器创建当前对象后,就会调用这个方法,完成赋值,方法的参数就是从容器里面获取的值。

    1
    2
    3
    4
    5
    //参数里面的Car就是从容器里面获取的
    @Autowired
    public void setCar(Car car){
    this.car = car;
    }
  • 标注在构造方法上。 如果组件只有一个有参构造器,则这里的@Autowired可以省略,参数位置的组件还是可以从容器中获取。

    1
    2
    3
    4
    5
    6
    7
    8
    @Autowired
    public Boss(Car car){
    this.car = car;
    }
    或者
    public Boss(Car car){
    this.car = car;
    }

默认加在ioc容器中的组件,容器在启动的时候会调用无参构造器来创建对象,然后进行初始化操作。

  • 标注在参数上。
1
2
3
public Boss(@Autowired Car car){
this.car = car;
}
  • 标注在属性上。
1
2
3
4
5
6
7
@Service
public class TestService{

//将TestDao组件注入到TestService里面来
@Autowired
private TestDao testDao;
}
1
2
3
4
5
6
7
8
@Service
public class TestService{

//required默认为true,表示容器中必须要有这个组件,否则报错
//required=false,表示容器中可以没有这个组件,如果没有这个组件,则获取的组件为null
@Autowired(required=false)
private TestDao testDao;
}
  • 默认优先按照组件类型去容器里面找对应的组件
  • 如果容器中该类型的组件有多个,再通过属性名作为id去容器里面查找
  • 自动装配默认一定要找到指定的组件并赋值好,如果没有找到组件,则会报错
  • 使用 @Autowired(required=false) 来表示该组件不是必须的,即没有的时候不会报错,会返回null

@Qualifier

该注解配合@Autowired使用,用于指定需要装配的组件的id,而不是使用属性名。

1
2
3
4
5
6
7
8
9
@Service
public class TestService{

//指定用哪一个组件
@Qualifier("testDao")
@Autowired
private TestDao testDao;

}

@Primary

在Spring自动装配的时候,默认使用首选的bean,即用该注解标注的bean,同时也可以使用@Qualifier指定装配哪个bean,这时默认的就会失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainConfig{

//这种情况下,使用@Autowired的时候就用这个bean
@Primary
@Bean
public BookDao bookDao(){
return new BookDao();
}

@Bean
public BookDao bookDao1(){
return new BookDao();
}
}

@Resource

JSR250里面的规范(Java规范)

默认是按照bean的名称(即属性名)来装配的,可以用name来表示需要装配哪一个bean。不能结合上面的@Qulifier和@Primary注解使用,也没有required功能。

1
2
3
4
5
6
@Service
public class TestService{

@Resource(name="testDao2")
private TestDao testDao;
}

@Inject

JSR330里面的规范(Java规范),它和Autowired注解功能类似,也能用@Primary功能,只不过没有required这个参数,即不支持required功能。

使用这个注解需要添加以下依赖:

1
2
3
4
5
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

@Profile

Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能。

第一步:指定运行环境

@Profile:指定组件在哪个环境下,才能被注册到容器中;

不指定@Profile的bean,在任何环境下都能注册;

可以标注在方法和类上。写在类上的时候,只有是在指定的环境,整个类里面所有的配置才能生效。
例如下面代码指定了dev环境。
13.JPG
注意:加了环境标识的bean,只有在这个环境被激活的时候才能注册到容器中,否则不能注册进容器。Spring的默认是default环境,在没有指定环境的时候默认环境生效。即

1
2
3
4
5
@Profile("default")
@Bean
public Car car(){
return new Car();
}

第二步:切换环境

  1. 命令行参数

    1
    2
    在VM arguments这一栏里面添加参数,激活dev环境
    -Dspring.profiles.active=dev
  2. 使用代码的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test
    public void contextLoads() {
    //第一步 创建一个ioc容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    //第二步 设置需要激活的环境
    applicationContext.getEnvironment().setActiveProfiles("test","dev");
    //第三步 注册主配置类
    applicationContext.register(ConfigOfProfile.class);
    //第四步 刷新启动容器
    applicationContext.refresh();
    }
  3. 在配置文件里面切换

    1
    2
    # 例如SpringBoot的配置文件 application.properties
    spring.profiles.active=dev

AOP

在程序运行期间,动态的将某段代码切入到指定的方法位置进行运行的编程方式,底层以动态代理实现

以SpringBoot为例
1.导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 定义一个业务逻辑类,在业务逻辑方法运行的时候进行日志打印(方法之前,之后,运行结果,运行异常)‘

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * @author 南风
    * @date 2019/8/15-17:18
    *业务逻辑类,即需要被切入的类
    */
    public class MathTest {

    public int div(int i,int j){
    System.out.println("除法被调用");
    return i/j;
    }
    }
  2. 定义一个切面类,类里面写切面方法(通知方法),需要在切面类上加上一个 @Aspect 注解,来告诉Spring哪个是切面类

    • 前置通知(@Before):在目标方法之前运行
    • 后置通知(@After):在目标方法运行之后运行,不论是正常结束还是异常结束
    • 返回通知(@AfterReturning):在目标方法正常返回时执行
    • 异常通知(@AfterThrowing):在目标方法抛出异常时执行
    • 环绕通知(@Around):动态代理,手动执行目标方法
      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
       /**
      * @author 南风
      * @date 2019/8/15-17:19
      * 切面类
      * @Aspect告诉Spring这是一个切面类
      */
      @Aspect
      public class MathAop {

      /**
      * 抽取公共的切入点表达式
      * 1. 在本类中直接使用 pointCut()
      * 2. 在外部类使用全类名 com.cdn.MathAop.pointCut()
      */
      @Pointcut("execution(public int com.cdn.MathTest.*(..))")
      public void pointCut(){
      }

      @Before("pointCut()")
      public void logBefore(JoinPoint joinPoint){
      //获取方法名
      String methodName = joinPoint.getSignature().getName();
      //获取参数列表
      Object[] args = joinPoint.getArgs();

      System.out.println("计算方法 "+methodName+" 之前,参数为:{"+Arrays.asList(args)+"}");
      }
      @After("pointCut()")
      public void logEnd(){
      System.out.println("计算方法结束");
      }

      /**
      * JoinPoint joinPoint 参数一定要放在参数表的第一位,否则无法识别
      * returning 用来接收返回值,它和该方法对应的参数绑定
      * @param result
      */
      @AfterReturning(value = "pointCut()",returning = "result")
      public void logReturn(JoinPoint joinPoint, Object result){
      System.out.println("计算方法正常返回,返回值:{"+result+"}");
      }

      /**
      * JoinPoint 参数和上面同理,放在参数列表的第一位
      * throwing 接收异常,和该方法的参数绑定
      * @param exception
      */
      @AfterThrowing(value = "pointCut()",throwing = "exception")
      public void logException(JoinPoint joinPoint,Exception exception){
      System.out.println("计算方法异常,异常信息为:{"+exception.getMessage()+"}");
      }
      }
  3. 将切面类和业务逻辑类都添加到容器中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Configuration
    public class ConfigOfAop {

    /**
    * 将需要被切入的逻辑类加入到容器中
    * @return
    */
    @Bean
    public MathTest mathTest(){
    return new MathTest();
    }

    /**
    * 将切面类加入到容器中
    * @return
    */
    @Bean
    public MathAop mathAop(){
    return new MathAop();
    }
    }
  4. 需要在配置类上加上 @EnableAspectJAutoProxy 注解来开启AspectJ自动代理

    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
    /**
    * @author 南风
    * @date 2019/8/15-17:01
    * @EnableAspectJAutoProxy 开启基于注解的AspectJ自动代理
    */
    @EnableAspectJAutoProxy
    @Configuration
    public class ConfigOfAop {

    /**
    * 将需要被切入的逻辑类加入到容器中
    * @return
    */
    @Bean
    public MathTest mathTest(){
    return new MathTest();
    }

    /**
    * 将切面类加入到容器中
    * @return
    */
    @Bean
    public MathAop mathAop(){
    return new MathAop();
    }
    }
-------- ♥感谢阅读♥ --------