Loading

Spring注解驱动开发之IOC

1 设计模式-工厂模式

  • 工厂模式是我们最常用的实例化对象的模式,它是用工厂的方法代替new创建对象的一种设计模式。
  • 以Mybatis的SqlSession接口为例,它有一个实现类DefaultSqlSession,如果我们要创建该接口的实例化对象:
SqlSession sqlSession = new DefaultSqlSession();
  • 可是,实际情况是,通常我们都要在创建SqlSession实例化的时候做点初始化工作,比如解析XML,封装连接数据库的信息等。
  • 在创建实例对象的时候,如果有一些不得不做的初始化操作时,我们首先想到的是,使用构造函数,这样生成实例对象的代码如下:
SqlSession sqlSession = new DefaultSqlSession(传入配置文件的路径);
  • 但是,如果创建SqlSession实例时所做的初始化工作不是像赋值这样简单的事情,可能是很长一段代码,如果也写入到构造函数中,那么代码将非常难看(后期需要重构)。
  • 为什么说代码非常难看,分析如下:初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也有悖于Java面向对象的原则,面向对象的封装和分派告诉我们,尽量将长的代码分派"切割"成每段,将每段再"封装"起来(减少段和段之间的耦合),这样,将风险分散,以后如果需要修改,只要修改每段,不会再发生牵一发而动全身的事情了。
  • 所以,Mybatis框架在使用的时候为我们提供了SqlSessionFactory工厂类,通过方法获取到SqlSession对象。同时方法有很多重载,用于实现不同的需求。这个方法就是openSession(),它支持传入Connection参数来保证连接的一致性,支持传入true或false来保证事务的提交时机等。

2 IOC和DI

2.1 IOC

  • IOC的含义是:控制反转。它不是一个技术,而是一种思想。其作用是用于削减代码间的耦合。它的实现思想就是利用了工厂设计模式,把创建对象从具体类中剥离出去,交由工厂来完成,从而降低代码间的依赖关系。

  • 耦合有如下的分类:

    • 1️⃣内容耦合:
      • 当一个模块直接修改或操作另一个模块的数据的时候,或一个模块不通过正常入口而转入另一个模块的时候,这样的耦合被称为内容耦合。
      • 内容耦合是最高程度的耦合,应该尽量避免使用。
    • 2️⃣公共耦合:
      • 两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。
      • 在具有大量公共耦合的结构中,确定究竟哪个模块给全局变量赋了一个特定的值是非常困难的。
    • 3️⃣外部耦合:
      • 一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,称之为外部耦合。
    • 4️⃣控制耦合:
      • 一个模块通过接口向另一个模块传递一个控制信号,接受信息的模块根据信号值而进行适当的动作,这种耦合称之为控制耦合。
    • 5️⃣标记耦合:
      • 如果一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和模块C之间存在一个标记耦合。
    • 6️⃣数据耦合:
      • 模块之间通过参数传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
    • 7️⃣非直接耦合:
      • 两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
  • 为什么要解耦?

    • 耦合是影响软件复杂程序和设计质量的一个重要因素,在设计上我们应该采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

2.2 DI

  • DI的全称是依赖注入。是Spring框架核心IOC的具体体现。
  • 举例说明:我们的程序在编写的时候,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IOC解耦只是降低它们之间的依赖关系,但不会消除依赖关系。比如:我们的业务层仍然会调用持久层的方法,那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。
  • 简而言之,就是坐等框架把持久层对象传入业务层,而不需要我们自己来获取。

3 Spring注解驱动入门

3.1 前言

  • Spring在2.5版本引入了注解配置的支持,同时从Spring 3版本开始,Spring JavaConfig项目提供的许多特性成为Spring框架核心的一部分。因此,可以使用Java而不是XML文件来定义应用程序的外部Bean。

3.2 注解驱动入门案例介绍

  • 需求:实现保存一条数据到数据库。
  • sql脚本:
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `money` double NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  • 要求:
    • 使用Spring框架中的JdbcTemplate和DruidDataSource。
    • 使用纯注解配置Spring的IOC。

3.3 案例实现

  • 导入相关jar包的Maven坐标
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>
  • db.properties
jdbc.url=jdbc:mysql://192.168.134.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=123456
  • 日志log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>
  • 编写配置类SpringConfig.java
package com.sunxiaping.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;

import javax.sql.DataSource;

/**
 * 配置类,相当于applicationContext.xml
 */
@Configuration
@PropertySource(value = "classpath:db.properties")
@ComponentScan(basePackages = "com.sunxiaping")
@EnableTransactionManagement //开启事务
public class SpringConfig {

    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 配置数据源
     *
     * @return
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate
     *
     * @return
     */
    @Bean
    public JdbcTemplate jdbcTemplate() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource());
        return jdbcTemplate;
    }

    /**
     * 配置事务管理器
     *
     * @return
     */
    @Bean
    public TransactionManager transactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    }

}
  • 编写实体类Account.java
package com.sunxiaping.bean;

import java.io.Serializable;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 21:09
 */
public class Account implements Serializable {

    private Integer id;

    private String name;

    private Double money;

    public Account() {
    }

    public Account(Integer id, String name, Double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  • 编写DAO接口和实现类:
  • AccountDao.java
package com.sunxiaping.dao;

import com.sunxiaping.bean.Account;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 21:27
 */
public interface AccountDao {

    void saveAccount(Account account);
}
  • AccountDaoImpl.java
package com.sunxiaping.dao.impl;

import com.sunxiaping.bean.Account;
import com.sunxiaping.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 21:28
 */
@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void saveAccount(Account account) {
        jdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (?,?)  ", account.getName(), account.getMoney());
    }
}
  • 编写业务层接口和实现类:
  • AccountService.java
package com.sunxiaping.service;

import com.sunxiaping.bean.Account;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 21:30
 */
public interface AccountService {

    void saveAccount(Account account);

}
  • AccountServiceImpl.java
package com.sunxiaping.service.impl;

import com.sunxiaping.bean.Account;
import com.sunxiaping.dao.AccountDao;
import com.sunxiaping.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 21:30
 */
@Service
@Transactional
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }
}

  • 测试:
package com.sunxiaping;

import com.sunxiaping.bean.Account;
import com.sunxiaping.config.SpringConfig;
import com.sunxiaping.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {

    public static void main(String[] args) {
        //创建容器(基于注解的创建方式)
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = context.getBean(AccountService.class);

        Account account = new Account();
        account.setName("张三");
        account.setMoney(12.3);

        accountService.saveAccount(account);
    }
}

4 IOC常用注解分析

4.1 用于注解驱动的注解

4.1.1 @Configuration注解

源码

  • @Configuration注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

说明

  • 作用:

    • @Configuration注解是Spring3.0版本之后才加入的。此注解是Spring支持注解驱动开发的一个标志。
    • @Configuration注解修饰的类表明是一个配置类,作用是替代Spring的applicationContext.xml文件。
    • 但其本质就是@Component注解,被@Configuration注解修饰的类,也会被存入到Spring的IOC容器中。
  • 属性:

    • value:用于存入Spring的IOC容器的Bean的id。
  • 使用场景:

    • 在注解驱动开发中,用于编写配置的类,通常可以使用此注解。
    • 一般情况下,我们的配置分为主从配置,@Configuration一般出现在主配置类上。
  • 注意:

    • 如果在注解驱动开发的时候,构建IOC容器使用的是传入字节码的构造函数,则此注解是可以省略。
    • 如果在注解驱动开发的时候,构建IOC容器使用的是传入扫描包的构造函数,则此注解不能省略。

应用示例

  • 示例:
  • SpringConfig.java
package com.sunxiaping.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;

import javax.sql.DataSource;

/**
 * 配置类,相当于applicationContext.xml
 */
@Configuration
@PropertySource(value = "classpath:db.properties")
@ComponentScan(basePackages = "com.sunxiaping")
@EnableTransactionManagement //开启事务
public class SpringConfig {

    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 配置数据源
     *
     * @return
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate
     *
     * @return
     */
    @Bean
    public JdbcTemplate jdbcTemplate() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource());
        return jdbcTemplate;
    }

    /**
     * 配置事务管理器
     *
     * @return
     */
    @Bean
    public TransactionManager transactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    }
}
  • 测试:
package com.sunxiaping;

import com.sunxiaping.bean.Account;
import com.sunxiaping.config.SpringConfig;
import com.sunxiaping.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {

    public static void main(String[] args) {
        //创建容器(基于注解的创建方式)
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = context.getBean(AccountService.class);

        Account account = new Account();
        account.setName("张三4");
        account.setMoney(12.3);
        
        accountService.saveAccount(account);
    }
}

4.1.2 @ComponentScan注解

源码

  • @ComponentScan注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/*.class";

    boolean useDefaultFilters() default true;

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}

说明

  • 作用:
    • 用于指定创建容器时要扫描的包。该注解在指定扫描的位置时,可以指定包名,也可以指定扫描的类。
    • 同时支持定义扫描规则,例如包含哪些或者排除哪些。
    • 同时,它还支持自定义Bean的命名规则。
  • 属性:
    • value:用于指定要扫描的包。当指定了包的名称以后,Spring会扫描指定的包及其子包下的所有类。
    • basePackages:和value的作用是一样的。
    • basePackageClasses:指定具体要扫描的类的字节码所在包及其子包。
    • nameGenerator:指定扫描Bean对象存入容器时的命名规则。详情参考5.4 BeanNameGenerator及其实现类
    • scopeResolver:用于出来并转换检测的Bean的作用范围。
    • scopedProxy:用于指定Bean生成时的代理方式。默认是Default,不使用代理。详情参看5.5 ScopedProxyMode枚举
    • resourcePattern:用于指定符合组件检测条件的类文件,默认是包扫描下的**/*.class
    • useDefaultFilters:是否对带有@Component、@Repository、@Service、@Controller注解的类开启检测,默认是开启的。
    • includeFilters:
      • 自定义组件扫描的过滤规则,用于扫描组件。
      • FilterType有5种类型:
        • ANNOTATION,注解类型,默认。
        • ASSIGNABLE_TYPE,指定固定类。
        • ASPECTJ:ASPECTJ类型。
        • REGEX:正则表达式。
        • CUSTOM:自定义类型。
      • 详细用法参考5.6 自定义组件扫描过滤规则
    • excludeFilters:自定义组件扫描的排除规则。
    • lazyInit:组件扫描时是否采用懒加载,默认不开启。
  • 细节:
    • 在Spring4.3版本之后还加入了一个@ComponentScans的注解,该注解就是支持配置多个@ComponentScan。

应用示例

  • 示例:basePackages属性
  • UserService.java
package com.sunxiaping.service;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 22:47
 */
public interface UserService {

    void saveUser();
}
  • UserServiceImpl.java
package com.sunxiaping.service.impl;

import com.sunxiaping.service.UserService;
import org.springframework.stereotype.Service;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 22:48
 */
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("保存用户信息");
    }
}
  • SpringConfig2.java
package com.sunxiaping.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 22:45
 */
@Configuration
//basePackages 扫描指定包及其子包下的所有组件
@ComponentScan(basePackages={"com.sunxiaping"})
public class SpringConfig2 {
}
  • 测试:
package com.sunxiaping;

import com.sunxiaping.config.SpringConfig2;
import com.sunxiaping.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {

    public static void main(String[] args) {
        //创建容器(基于注解的创建方式)
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig2.class);
        UserService userService = context.getBean(UserService.class);
        System.out.println("userService = " + userService);
    }
}
  • 示例:basePackageClasses属性
  • UserService.java
package com.sunxiaping.service;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 22:47
 */
public interface UserService {

    void saveUser();
}
  • UserServiceImpl.java
package com.sunxiaping.service.impl;

import com.sunxiaping.service.UserService;
import org.springframework.stereotype.Service;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 22:48
 */
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("保存用户信息");
    }
}
  • SpringConfig2.java
package com.sunxiaping.config;

import com.sunxiaping.service.UserService;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 22:45
 */
@Configuration
//basePackageClasses 扫描字节码所在包及其子包下的所有组件
@ComponentScan(basePackageClasses = UserService.class)
public class SpringConfig2 {
}
  • 测试:
package com.sunxiaping;

import com.sunxiaping.config.SpringConfig2;
import com.sunxiaping.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {

    public static void main(String[] args) {
        //创建容器(基于注解的创建方式)
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig2.class);
        UserService userService = context.getBean(UserService.class);
        System.out.println("userService = " + userService);
    }
}
  • 示例:
  • SpringConfig2.java
package com.sunxiaping.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-26 22:45
 */
@Configuration
//默认情况下扫描的是@ComponentScan修饰类所在包及其子包下的所有组件
@ComponentScan
public class SpringConfig2 {
}

测试:

package com.sunxiaping;

import com.sunxiaping.config.SpringConfig2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {

    public static void main(String[] args) {
        //创建容器(基于注解的创建方式)
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig2.class);
        String[] definitionNames = context.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }
}

4.1.3 @Bean注解

源码

  • @Bean注解:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {

	@AliasFor("name")
	String[] value() default {};

	@AliasFor("value")
	String[] name() default {};

	@Deprecated
	Autowire autowire() default Autowire.NO;

	boolean autowireCandidate() default true;

	String initMethod() default "";

	String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}

说明

  • 作用:
    • @Bean写在方法上,表示把当前方法的返回值存入到Spring的IOC容器中。
    • @Bean写在注解上,作为元注解来使用。
  • 属性:
    • name:用于指定存入Spring容器中Bean的标识。支持多个标识。当不指定该属性的时候,默认值是当前方法的名称。
    • value:此属性是在4.3.3版本之后加入的。它和name属性的作用是一样的。
    • autowireCandidate:用于指定是否支持自动按类型注入到其他Bean中。只影响@Autowired注解的使用。不影响@Resource注解注入。默认值为true,允许使用自动按类型注入。
    • initMethod:用于指定初始化方法。
    • destroyMethod:用于指定销毁方法。
  • 使用场景:
    • 通常情况下,在基于注解的配置中,我们用把一个类存入Spring的IOC容器中,首先考虑的是使用@Component以及它的衍生注解。但是如果遇到要存入容器的Bean对象不是我们写的类,此时无法在类上添加@Component注解,这个时候就可以使用@Bean注解了。

应用示例

  • 示例:
  • SpringConfig.java
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * 标注这是一个注解配置类
 */
@Configuration
@ComponentScan(basePackages = "com.sunxiaping")
public class SpringConfig {

    @Resource(name = "dataSource")
    private DataSource dataSource;

    @Bean(autowireCandidate = false)
    public DataSource dataSource() {
        return new DriverManagerDataSource();
    }

    @Bean
    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource);
    }

}
  • 测试:
package com.sunxiaping;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class SpringTest {

    public static void main(String[] args) {
        //创建容器(基于注解的创建方式)
        ApplicationContext context = new AnnotationConfigApplicationContext("com");
        JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
        System.out.println("jdbcTemplate = " + jdbcTemplate);
    }
}

4.1.4 @Import注解

源码

  • @Import注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	Class<?>[] value();
}

说明

  • 作用:
    • 该属性是写在类上的,通常都是和注解驱动的配置类一起使用的。
    • 其作用是引入其他的配置类。使用了此注解之后,可以使我们的注解驱动开发和早期XML配置一样,分别配置不同的内容,使得配置更加清晰。
    • 同时指定了此注解之后,被引入的类上可以不再使用@Configuration注解、@Component等注解。
  • 属性:
    • value:用于指定其他配置类的字节码。它支持指定多个配置类。关于ImportSelector和ImportBeanDefinitionRegister请查看5.7 @Import注解的高级分析
  • 使用场景:
    • 当我们在使用注解驱动开发时,由于配置项过多,如果都写在一个类里面,配置结构和内容将会杂乱不堪,此时使用@Import注解可以把配置项进行分门别类进行配置。

应用示例

  • 示例:
  • JdbcConfig.java
package com.sunxiaping.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

/**
 * 连接数据库相关的配置
 *
 * @author <a href="mailto:1900919313@qq.com">weiwei.xu</a>
 * @version 1.0
 * 2020-07-28 22:31
 */
@Configuration
@EnableTransactionManagement
public class JdbcConfig {
    @Bean
    public DataSource dataSource(){
        return new DriverManagerDataSource();
    }

    @Bean
    public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}
  • SpringConfig.java
package com.sunxiaping.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * Spring的配置类
 */
@Configuration
@Import(JdbcConfig.class)
public class SpringConfig {
    
}
  • 测试:
package com.sunxiaping;

import com.sunxiaping.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {

    public static void main(String[] args) {
        //创建容器(基于注解的创建方式)
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

        String[] definitionNames = context.getBeanDefinitionNames();

        for (String definitionName : definitionNames) {
            System.out.println(definitionName);
        }
    }
}

4.1.5 @PropertySource注解

源码

  • @PropertySource注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

	/**
	 * Indicate the name of this property source. If omitted, the {@link #factory()}
	 * will generate a name based on the underlying resource (in the case of
	 * {@link org.springframework.core.io.support.DefaultPropertySourceFactory}:
	 * derived from the resource description through a corresponding name-less
	 * {@link org.springframework.core.io.support.ResourcePropertySource} constructor).
	 * @see org.springframework.core.env.PropertySource#getName()
	 * @see org.springframework.core.io.Resource#getDescription()
	 */
	String name() default "";

	/**
	 * Indicate the resource location(s) of the properties file to be loaded.
	 * <p>Both traditional and XML-based properties file formats are supported
	 * &mdash; for example, {@code "classpath:/com/myco/app.properties"}
	 * or {@code "file:/path/to/file.xml"}.
	 * <p>Resource location wildcards (e.g. *&#42;/*.properties) are not permitted;
	 * each location must evaluate to exactly one {@code .properties} resource.
	 * <p>${...} placeholders will be resolved against any/all property sources already
	 * registered with the {@code Environment}. See {@linkplain PropertySource above}
	 * for examples.
	 * <p>Each location will be added to the enclosing {@code Environment} as its own
	 * property source, and in the order declared.
	 */
	String[] value();

	/**
	 * Indicate if failure to find the a {@link #value() property resource} should be
	 * ignored.
	 * <p>{@code true} is appropriate if the properties file is completely optional.
	 * Default is {@code false}.
	 * @since 4.0
	 */
	boolean ignoreResourceNotFound() default false;

	/**
	 * A specific character encoding for the given resources, e.g. "UTF-8".
	 * @since 4.3
	 */
	String encoding() default "";

	/**
	 * Specify a custom {@link PropertySourceFactory}, if any.
	 * <p>By default, a default factory for standard resource files will be used.
	 * @since 4.3
	 * @see org.springframework.core.io.support.DefaultPropertySourceFactory
	 * @see org.springframework.core.io.support.ResourcePropertySource
	 */
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

说明

  • 作用:
    • 用于指定读取资源文件的位置。
    • 需要注意的是,它不仅支持properties,也支持XML文件,并且通过YAML解析器,配合自定义PropertySourceFactory实现解析yml配置文件(详情参考5.8 自定义PropertySourceFactory实现YAML文件解析)。
  • 属性:
    • name:指定资源的名称。如果没有指定,将根据基础资源描述生产。
    • value:指定资源的位置。可以是类路径,也可以是文件路径。
    • ignoreResourceNotFound:指定是否忽略资源文件有没有,默认是false,也就是说当前资源文件不存在的时候Spring启动将会报错。
    • encoding:指定解析资源文件使用的字符集。当有中文的时候,需要指定中文的字符集。
    • factory:指定读取对应资源文件的工厂类。默认是PropertySourceFactory。
  • 使用场景:
    • 我们在实际开发中,使用注解驱动开发后,XML配置文件就没有了,此时一些配置如果直接写在类中,会造成和Java源码的紧密耦合,修改起来不方便。此时一些配置可以使用properties或yml配置就变得很灵活方便了。

注意事项

  • Spring在4.3之前的@PropertySource注解中是没有encodingfactory属性的,所以需要配置资源文件解析器到Spring的IOC容器中。
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

/**
 * Spring的配置类
 */
@Configuration
@Import(JdbcConfig.class)
public class SpringConfig {


    /**
     * 注册资源文件的解析器到Spring的IOC容器中
     * 针对的是Spring4.3之前的版本
     *
     * @return 资源文件的解析器
     */
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

}

示例

  • 示例:
  • db.properties
jdbc.url=jdbc:mysql://192.168.134.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=123456
  • Spring的配置类SpringConfig.java
package com.sunxiaping.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;

import javax.sql.DataSource;

/**
 * 配置类,相当于applicationContext.xml
 */
@Configuration
@PropertySource(value = "classpath:db.properties")
@ComponentScan(basePackages = "com.sunxiaping")
@EnableTransactionManagement //开启事务
public class SpringConfig {

    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 配置数据源
     *
     * @return
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate
     *
     * @return
     */
    @Bean
    public JdbcTemplate jdbcTemplate() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource());
        return jdbcTemplate;
    }

    /**
     * 配置事务管理器
     *
     * @return
     */
    @Bean
    public TransactionManager transactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    }

}

4.2 注解驱动开发之注入时机和设定注入条件的注解

4.2.1 @DependsOn注解

源码

  • @DependsOn注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {

	String[] value() default {};

}

说明

  • 作用:
    • 用于指定某个类的创建依赖的Bean对象先创建。Spring中没有指定Bean的加载顺序,使用此注解则可以执行Bean的加载顺序(在基于注解配置中,是按照类中方法的书写顺序决定的)。
  • 属性:
    • value:用于指定Bean的唯一标识。被指定的Bean会在当前Bean创建之前加载。
  • 使用场景:
    • 在观察者模式中,分为事件、事件源和监听器。一般情况下,我们的监听器负责监听事件源,当事件源触发了事件之后,监听器就要捕获,并且做出相应的处理。以此为前提,我们肯定希望监听器的创建时间在事件源之前,此时就可以使用此注解了。

应用示例

  • 示例:
  • 事件源:
package com.sunxiaping.spring5.event;

import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

/**
 * 事件源对象
 */
@Component
@DependsOn(value = "eventTListener" )
public class EventSource {

    public EventSource() {
        System.out.println("事件源对象创建了");
    }
}
  • 监听器
package com.sunxiaping.spring5.event;

import org.springframework.stereotype.Component;

/**
 * 事件监听器
 */
@Component
public class EventTListener {

    public EventTListener() {
        System.out.println("事件监听器创建了");
    }
}
  • Spring的配置类
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan(value = "com.sunxiaping.spring5")
public class SpringConfig {

}
  • 测试:
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.stream.Stream;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Autowired
    private ApplicationContext context;

    @Test
    public void test() {
        String[] names = context.getBeanDefinitionNames();
        Stream.of(names).forEach(System.out::println);
    }

}

4.2.2 @Lazy注解

源码

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {

	/**
	 * Whether lazy initialization should occur.
	 */
	boolean value() default true;

}

说明

  • 作用:
    • 用于指定单例Bean对象的创建时机。在没有使用此注解的时候,单例Bean的生命周期和容器相同。但是当使用了此注解以后,单例对象的创建时机就变成了第一次使用创建。注意:这不是延迟加载思想(因为不是每次使用时创建,只是第一次使用的时候创建。)
  • 属性:
    • value:指定是否采用延迟初始化。默认是true,表示开启。
  • 使用场景:
    • 在实际开发中,当我们的Bean是单例对象的时候,并不是每个都需要一开始就加载到IOC容器中,有些对象可以在真正使用的时候再加载,当有此需要时,可以使用此注解。值得注意的是,此注解只对单例Bean对象起作用,当指定了@Scope注解的prototype属性值后,此注解就不起作用。

应用示例

  • 示例:
  • LogUtil.java
package com.sunxiaping.spring5.log;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Lazy
@Component
public class LogUtil {

    public LogUtil() {
        System.out.println("(*^▽^*)LogUtil对象创建成功啦(*^▽^*)");
    }

    public void printLog(){
        System.out.println("~~~~打印日志啦~~~~");
    }

}
  • Spring配置类
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan(value = "com.sunxiaping.spring5")
public class SpringConfig {

}
  • 测试:
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import com.sunxiaping.spring5.log.LogUtil;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Spring5Test {
    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

        LogUtil logUtil = context.getBean(LogUtil.class);
        logUtil.printLog();
    }

}

4.2.3 @Conditional注解

源码

  • @Conditional注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

说明

  • 作用:
    • 根据条件选择注入Bean对象。
  • 属性:
    • value:用于提供一个Condition接口的实现类,实现类中需要编写具体的代码实现注入的条件。
  • 使用场景:
    • 当我们在开发时,可能会使用多个平台来测试,例如我们的测试数据库分别部署到了Linux和Windows两个操作系统上,现在根据我们的工程运行环境连接数据库。此时就可以使用此注解。同时基于此注解引出的@Profile注解,就是根据不同的环境,加载不同的配置信息。(详情参考 5.9 @Profile注解的使用

应用示例

  • 示例:
  • WindowsCondition.java
package com.sunxiaping.spring5.condition;


import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

/**
 * Win环境
 */
public class WindowsCondition implements Condition {
    /**
     * 是否注册到IOC容器中的核心方法
     *
     * @param context
     * @param metadata
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取IOC使用的BeanFactory对象
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //获取Bean定义信息的注册器
        BeanDefinitionRegistry registry = context.getRegistry();
        //获取环境信息
        Environment environment = context.getEnvironment();
        if (environment instanceof StandardEnvironment) {
            StandardEnvironment standardEnvironment = (StandardEnvironment) environment;
            Map<String, Object> systemProperties = standardEnvironment.getSystemProperties();
            systemProperties.forEach((key, value) -> {
                System.out.println("key=" + key + ",value=" + value);
            });
        }
        //获取当前系统的名称
        String os = environment.getProperty("os.name");
        if (os.startsWith("win") || os.startsWith("Win")) {
            return true;
        }
        return false;
    }
}
  • LinuxCondition.java
package com.sunxiaping.spring5.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class LinuxCondition implements Condition {
    /**
     * 是否注册到IOC容器中的核心方法
     *
     * @param context
     * @param metadata
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String property = context.getEnvironment().getProperty("os.name");
        if (property.equals("Linux")) {
            return true;
        }
        return false;
    }
}
  • SpringConfig.java
package com.sunxiaping.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sunxiaping.spring5.condition.LinuxCondition;
import com.sunxiaping.spring5.condition.WindowsCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * Spring的配置类
 */
@Configuration
public class SpringConfig {

    @Bean
    @Conditional(WindowsCondition.class)
    public DataSource windowsDataSource() {
        System.out.println("Windows环境下的DataSource");
        return new DruidDataSource();
    }

    @Bean
    @Conditional(LinuxCondition.class)
    public DataSource linuxDataSource() {
        System.out.println("Linux环境下的DataSource");
        return new DruidDataSource();
    }

}
  • 测试:
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Autowired
    private DataSource dataSource;

    @Test
    public void test(){
        System.out.println(dataSource);
    }
}

4.3 用于创建对象的注解

4.3.1 @Component及其衍生注解

源码

  • @Component注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";

}
  • @Repositroy注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}
  • @Service注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}
  • @Controller注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

说明

  • 作用:
    • 这四个注解都是用于修饰类的。是用于把当前类创建一个对象,存入Spring的IOC容器中。在实例化的时候,首选默认的无参构造函数。同时支持带参构造,前提是构造函数依赖必须有值,否则抛出异常。
  • 属性:
    • value:用于指定存入容器的Bean的id。当不指定时,默认值为当前类的名称。
  • 使用场景:
    • 当我们需要把自己编写的类注入到IOC容器中,就可以使用以上四个注解实现。以上四个注解中@Component注解通常在非三层对象中。而@Controller、@Service和@Controller注解一般是针对三层对象使用的,提供更加精确的语义化配置。
    • 需要注意的是,Spring在注解驱动开发的时候,要求必须先接管类对象,然后会处理类中的属性和方法。如果类没有被Spring接管,那么里面的属性和方法上的注解都不会被解析。

应用示例

  • 略。

4.4 用于注入数据的注解

4.4.1 @Autowired注解

源码

  • @Autowired注解:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}

说明

  • 作用:
    • 自动按照类型注入。当IOC容器中有且仅有一个类型匹配的时候可以直接注入成功。当超过一个匹配的时候,则使用变量名称(写在方法上就是方法名称)作为Bean的id,在符合类型的Bean中再次匹配,能匹配上就可以注入成功。当匹配不上时,是否报错看required属性的取值。
  • 属性:
    • required:是否必须注入成功。默认是true,表示必须注入成功。当取值为true的时候,注入不成功会报错。
  • 使用场景:
    • 此注解的使用场景非常之多,在实际开发中应用广泛。通常情况下我们自己的写的类注入依赖Bean对象的时候,都可以采用此注解。

应用示例

  • 略。

4.4.2 @Qualifier注解

源码

  • @Qualifier注解:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {

	String value() default "";

}

说明

  • 作用:
    • 当使用自动按类型注入的时候,遇到有多个类型匹配的时候,就可以使用此注解来明确注入那个Bean对象。注意它通常情况下必须和@Autowired注解一起使用。
  • 属性:
    • value:用于指定Bean的唯一标识。
  • 使用场景:
    • 在我们的项目开发中,很多时候会使用到消息队列,我们以ActiveMQ为例。当和Spring整合的时候,Spring框架提供了一个JmsTemplate对象,它既可以用于发送点对点模型消息也可以发送主题模型消息。如果项目中两种消息模型都用上了,那么针对不同的代码,将会注入不同的JmsTemplate,而容器中出现两个之后,就可以使用此注解注入。当然也可以不用,我们只需要把要注入的变量名称改为和要注入的Bean的id一致即可。

应用示例

  • 略。

4.4.3 @Inject注解

源码

  • @Inject注解:
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}

说明

  • 作用:

    • 它也是用于建立以来关系的。和@Resource和@Autowired的作用是一样的。但是,在使用之前,必须导入坐标:
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
    
    • @Autowired:来源于Spring框架本身。默认是byType自动装配,当整合了@Qualifier注解之后,由@Qualifier实现byName装配。它有一个required属性,用于指定是否必须注入成功。
    • @Resource:来源于JSR250规范。在没有指定name属性时按照byType自动装配,当指定了name属性之后,采用byName方式自动装配。
    • @Inject:来源于JSR330规范。它不支持任何属性,但是可以配合@Qualifier和@Primary注解使用,同时,它默认采用过得是byType装配,当指定了JSR330规范中的@Named注解之后,变成了byName装配。
  • 属性:无

  • 使用场景:

    • 在使用@Autowired注解的地方,都可以替换成@Inject。它也可以出现在方法上,构造函数上和字段上,但是需要注意的是,因为JRE无法决定构造方法注入的优先级,所以规范中规范类中只能有一个构造方法带@Inject注解。

应用示例

  • 略。

4.4.4 @Value注解

源码

  • @Value注解:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {

	/**
	 * The actual value expression &mdash; for example, <code>#{systemProperties.myProp}</code>.
	 */
	String value();

}

说明

  • 作用:
    • 用于注入基本数据类型和String类型的数据。它支持Spring的EL表达式,可以通过${}的方式获取配置文件中的数据。配置文件支持properties、xml和yml格式。
  • 属性:
    • value:指定注入的数据或者Spring的EL表达式。
  • 使用场景:
    • 在实际开发中,像连接数据库的配置,发送邮件的配置等待,都可以使用配置文件配置起来。此时读取配置文件就可以借助Spring的EL表达式读取。

应用示例

  • 略。

4.4.5 @Resource注解

源码

  • @Resource注解:
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {
    /**
     * The JNDI name of the resource.  For field annotations,
     * the default is the field name.  For method annotations,
     * the default is the JavaBeans property name corresponding
     * to the method.  For class annotations, there is no default
     * and this must be specified.
     */
    String name() default "";

    /**
     * The name of the resource that the reference points to. It can
     * link to any compatible resource using the global JNDI names.
     *
     * @since 1.7, Common Annotations 1.1
     */

    String lookup() default "";

    /**
     * The Java type of the resource.  For field annotations,
     * the default is the type of the field.  For method annotations,
     * the default is the type of the JavaBeans property.
     * For class annotations, there is no default and this must be
     * specified.
     */
    Class<?> type() default java.lang.Object.class;

    /**
     * The two possible authentication types for a resource.
     */
    enum AuthenticationType {
	    CONTAINER,
	    APPLICATION
    }

    /**
     * The authentication type to use for this resource.
     * This may be specified for resources representing a
     * connection factory of any supported type, and must
     * not be specified for resources of other types.
     */
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;

    /**
     * Indicates whether this resource can be shared between
     * this component and other components.
     * This may be specified for resources representing a
     * connection factory of any supported type, and must
     * not be specified for resources of other types.
     */
    boolean shareable() default true;

    /**
     * A product-specific name that this resource should be mapped to.
     * The <code>mappedName</code> element provides for mapping the
     * resource reference to the name of a resource known to the
     * applicaiton server.  The mapped name could be of any form.
     * <p>Application servers are not required to support any particular
     * form or type of mapped name, nor the ability to use mapped names.
     * The mapped name is product-dependent and often installation-dependent.
     * No use of a mapped name is portable.</p>
     */
    String mappedName() default "";

    /**
     * Description of this resource.  The description is expected
     * to be in the default language of the system on which the
     * application is deployed.  The description can be presented
     * to the Deployer to help in choosing the correct resource.
     */
    String description() default "";
}

说明

  • 作用:

    • 此注解来孕育JSR规范,其作用是找到依赖的组件注入到应用来,它利用了JNDI技术查找所需的资源,但是,在使用之前,必须导入坐标:
    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
    </dependency>
    
    • 默认情况下,如果所有的属性不指定,@Resource注解是按照byType的方式装配Bean对象。如果指定了name,没有指定type,没有指定type,则采用byName。如果没有指定name,而是指定了type,则安装byType装配Bean对象。当name和type都指定了,两个都会校验,有任何一个不符合条件都会报错。
  • 属性:

    • name:资源的JNDI名称。在Sprig注入的时候,指定Bean的唯一标识。
    • type:指定Bean的类型。
    • lookup:引用指向的资源的名称。它可以使用全局JNDI名称链接到任何兼容的资源。
    • authenticationType:指定资源的身份验证类型。它只能为任何受支持类型的连接工厂的资源指定此选项,而不能为其他类型的资源指定此选项。
    • shareable:指定此资源是否可以在此组件和其他组件之间共享。
    • mappedName:指定资源的映射名称。
    • description:指定资源的描述。
  • 使用场景:

    • 当我们某个类的依赖Bean在IOC容器中存在多个的时候,可以使用此注解指定特定的Bean对象注入。当然我们也可以使用@Autowired配置@Qualifier注解注入。

应用示例

  • 略。

4.4.6 @Primary注解

源码

  • @Primary注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {

}

说明

  • 作用:
    • 用于指定Bean的注入优先级,被@Primary修饰的Bean对象优先注入。
  • 属性:无。
  • 使用场景:
    • 当我们的依赖对象,有多个存在的时候,@Autowired注解已经无法完成功能,此时我们首先想到的是@Qualifier注解指定依赖Bean的id。但是此时就产生了,无论有多少个Bean,每次都会使用指定的Bean注入。但是当我们使用@Primary,表示优先使用@Primary注解的Bean,但是当不存在的时候还会使用其他的。

应用示例

  • 略。

4.5 和生命周期以及作用范围相关的注解

4.5.1 @Scope注解

源码

  • @Scope注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * Alias for {@link #scopeName}.
	 * @see #scopeName
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 * Specifies the name of the scope to use for the annotated component/bean.
	 * <p>Defaults to an empty string ({@code ""}) which implies
	 * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
	 * @since 4.2
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
	 * @see #value
	 */
	@AliasFor("value")
	String scopeName() default "";

	/**
	 * Specifies whether a component should be configured as a scoped proxy
	 * and if so, whether the proxy should be interface-based or subclass-based.
	 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
	 * @see ScopedProxyMode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

说明

  • 作用:

    • 指定Bean的作用范围。
  • 属性:

    • value:指定作用范围的取值。默认是""。但是在Spring初始化容器的时候,会借助ConfigurableBeanFactory接口中的类成员 String SCOPE_SINGLETON = "singleton";

    • scopeName:它和value的作用是一样的。

    • proxyMode:它是指定Bean对象的代理方式。

  • 使用场景:

    • 在实际开发中,我们的Bean对象默认都是单例的。通常情况下,被Spring管理的Bean都是使用单例模式来创建。但是也有例外,例如Struts2框架中的Action,由于有模型驱动和OGNL表达式的原因,就必须配置成多例。不过,谁现在还用Struts2呢,Spring不香吗?

应用示例

  • 略。

4.5.2 @PostConstruct注解

源码

  • @PostConstruct注解:
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {

}

说明

  • 作用:
    • 用于指定Bean对象的初始化方法。
  • 属性:无。
  • 使用场景:
    • 在Bean对象创建完成后,需要对Bean中的成员进行一些初始化的操作时,就可以使用此注解配置一个初始化方法,完成一些初始化的操作。

应用示例

  • 略。

4.5.3 @PreDestory注解

源码

  • @PreDestory注解:
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
	
}

说明

  • 作用:
    • 用于指定Bean对象的销毁方法。
  • 属性:无。
  • 使用场景:
    • 在Bean对象销毁之前,可以进行一些清理操作。

应用示例

  • 略。

5 Spring高级之IOC的深入剖析

5.1 Spring中的BeanFactory

5.1.1 BeanFactory的类图

BeanFactory的类图

5.1.2 工厂详解

5.1.2.1 BeanFactory

  • BeanFactory的整个设计还是比较简洁、直观的,其中近一半是获取Bean对象的各种方法,另外就是对Bean属性的获取和判定,该接口仅仅是定义了IOC容器的最基本的形式,具体实现由子类来实现。

5.1.2.2 HierarchicalBeanFactory

  • HierarchicalBeanFactory,中文译为“分层的”,它相对于BeanFactory增加了对父BeanFactory的获取,子容器可以通过接口方法访问父容器,让容器的设计具备了层次性。这种层次性增强了容器的扩展性和灵活性,我们可以通过编程的方式为一个已有的容器添加一个或多个子容器,从而实现一些特殊的功能。层次容器有一个特点就是子容器对于父容器来说是透明的,而子容器则能感知父容器的存在。典型的应用场景就是SpringMVC,控制层的Bean位于子容器中,并将业务层和持久层的Bean所在的容器设置为父容器,这样的设计可以让控制层的Bean访问业务层和持久层的Bean,反之则不行,从而在容器层面对三层架构设计提供了支持。

5.1.2.3 ListableBeanFactory

  • ListableBeanFactory引入了获取容器中Bean的配置信息的若干方法,比如获取容器中Bean的个数,获取容器中所有Bean的名称列表,按照目标类型获取Bean名称以及检查容器中是否包含指定名称的Bean等等。
  • Listable,中文译为“可列举的”,对于容器而言,Bean的定义和属性是可列举的对象。

5.1.2.4 AutowireCapableBeanFactory

  • AutowireCapableBeanFactory提供创建Bean、自动注入、初始化以及应用Bean的后置处理器等功能。自动注入让配置变得更简单,也让注解配置成为可能,Spring提供了四种自动注入类型。
    • byName:
      • 根据名称自动装配。
      • 假设Bean A有一个名为b的属性,如果容器中刚好有一个Bean的名称是b,则将该Bean装配给Bean A的b属性。
    • byType:
      • 根据类型自动装配。
      • 假设Bean A有一个类型为b的属性,如果容器中刚好有一个b类型的Bean,则使用该Bean装配A的对应属性。
    • constructor:
      • 仅针对构造方法注入而言,类似于byType。
      • 如果Bean A有一个构造方法,构造方法包含一个B类型的入参,如果容器中有一个B类型的Bean,则使用该Bean作为入参,如果找不到,则抛出异常。
    • autodetect:
      • 根据Bean的自省机制决定采用byType还是constructor进行自动装配。
      • 如果Bean提供了默认的构造函数,则采用byType,否则采用constructor。

5.1.2.5 ConfigurableBeanFactory

  • ConfigurableBeanFactory提供配置Factory的各种方法,增强了容器的可定制性,定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法。

5.1.2.6 DefaultListableBeanFactory

  • DefaultListableBeanFactory是一个非常重要的类,它包含了IOC容器应该具备的重要功能,是容器完整功能的一个基本实现,XmlBeanFactory是一个典型的由该类派生出来的Factory,并且只是增加了加载XML配置资源的逻辑,而容器相关的特性则全部由DefaultListableBeanFactory来实现。

5.1.2.7 ApplicationContext

  • ApplicationContext是Spring为开发者提供的高级容器形式,也是我们初始化Spring容器的常用方式,除了简单容器所具备的功能外,ApplicationContext还提供了许多额外功能来降低开发人员的开发量,提升框架的使用效率。
  • 这些额外的功能主要包括以下几个方面:
    • 国际化支持:ApplicationContext实现了org.springframework.context.MessageSource接口,该接口为容器提供国际化消息访问功能,支持具备多语言版本需求的应用开发,并提供了多种实现来简化国际化资源的装载和获取。
    • 发布应用上下文事件:ApplicationContext实现了org.springframework.context.ApplicationEventPublisher接口,该接口让容器拥有帆布应用上下文事件的功能,包括容器启动、关闭事件等,如果一个Bean需要接受容器事件,则只需要实现ApplicationListener接口即可,Spring会自动扫描对应的监听器配置,并注册成为观察者。
    • 丰富的资源获取的方式:ApplicationContext实现了org.springframework.core.io.support.ResourcePatternResolver接口,ResourcePatternResolver接口的实现类PathMatchingResourcePatternResolver,可以让我们采用Ant风格的资源路径去加载配置文件。

5.1.2.8 ConfigurableApplicationContext

  • ConfigurableApplicationContext中主要增加了refresh和close两个方法,从而为应用上下文提供了启动、刷新和关闭的能力。其中refresh方法是高级容器的核心方法,方法中概况了高级容器初始化的主要流程(包含简单容器的全部功能,以及高级容器特有的扩展功能)。

5.1.2.9 WebApplicationContext

  • WebApplicationContext是为Web应用定时的上下文,可以基于WEB容器来实现配置文件的加载,以及初始化工作。对于非WEB应用而言,Bean只有singleton和prototype两种作用域,而在WebApplicationContext中则新增了request、session、globalSession以及application四种作用域。
  • WebApplicationContext将整个应用上下文对象以属性的形式放置到ServletContext中,所以在WEB应用中,我们可以通过WebApplicationContextUtils的getWebApplicationContext(ServletContext sc)方法,从ServletContext中获取到ApplicationContext实例。为了支持这一特性,WebApplicationContext中定义了一个常量:String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";,并在初始化应用上下文的时候将该常量设置为key,将WebApplicationContext的示例存放到ServletContext的属性列表中,当我们在调用WebApplicationContextUtils的getWebApplicationContext方法时,本质上就是调用ServletContext的getAttribute(String name)方法,只不过Spring会对获取的结果做一些校验。

5.1.2.10 高级容器的一些具体实现类型

  • AnnotationConfigApplicationContext是基于注解驱动开发的高级容器类,该类中提供了AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner两个成员,AnnotatedBeanDefinitionReader用于读取注解创建Bean的定义信息,ClassPathBeanDefinitionScanner负责扫描指定包获取Bean的定义信息。

  • ClasspathXmlApplicationContext是基于XML配置的高级容器类,它用于加载类路径下的配置文件。

  • FileSystemXmlApplicationContext是基于XML配置的高级容器类,它用于加载文件系统中的配置文件。

  • AnnotationConfigWebApplicationContext是注解驱动开发web应用的高级容器类。

5.2 Spring中的BeanDefinition

5.2.1 BeanDefinition类图

BeanDefinition类图

5.2.2 说明

  • 现实生活中的容器是用来装物品的,Spring的容器也不例外,Spring中的物品指的就是Bean。我们通常对于Bean的理解就是一个个配置在配置文件中的<bean />标签,或者被注解的类,但是这些都是Bean的静态表示形式,是还没有放入容器的物料,最终加载到容器中的是一个个的BeanDefinition实例。
  • BeanDefinition主要有RootBeanDefinition、ChildBeanDefinition和GenericBeanDefinition三个主要的实现。

5.2.3 Bean的定义信息详解

  • AbstractBeanDefinition.java
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {

	//常量省略

	@Nullable
	private volatile Object beanClass;

	@Nullable
	private String scope = SCOPE_DEFAULT;

	private boolean abstractFlag = false;

	@Nullable
	private Boolean lazyInit;

	private int autowireMode = AUTOWIRE_NO;

	private int dependencyCheck = DEPENDENCY_CHECK_NONE;

	@Nullable
	private String[] dependsOn;

	private boolean autowireCandidate = true;

	private boolean primary = false;

	private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();

	@Nullable
	private Supplier<?> instanceSupplier;

	private boolean nonPublicAccessAllowed = true;

	private boolean lenientConstructorResolution = true;

	@Nullable
	private String factoryBeanName;

	@Nullable
	private String factoryMethodName;

	@Nullable
	private ConstructorArgumentValues constructorArgumentValues;

	@Nullable
	private MutablePropertyValues propertyValues;

	private MethodOverrides methodOverrides = new MethodOverrides();

	@Nullable
	private String initMethodName;

	@Nullable
	private String destroyMethodName;

	private boolean enforceInitMethod = true;

	private boolean enforceDestroyMethod = true;

	private boolean synthetic = false;

	private int role = BeanDefinition.ROLE_APPLICATION;

	@Nullable
	private String description;

	@Nullable
	private Resource resource;
 	
    //省略
}

5.2.4 总结

  • BeanDefinition是容器对于Bean配置的内部表示,Spring将各个Bean的BeanDefinition实例注册记录在BeanDefinitionRegistry中,该接口定义了对BeanDefinition的各种CRUD操作,类似于内存数据库,其实现类SimpleBeanDefinitionRegistry主要以Map为主。

AnnotationConfigApplicationContext的类图

5.3 注解驱动执行过程分析

5.3.1 使用配置类字节码的构造函数

构造函数源码

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

	private final AnnotatedBeanDefinitionReader reader;

	private final ClassPathBeanDefinitionScanner scanner;


	/**
	 * Create a new AnnotationConfigApplicationContext that needs to be populated
	 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
	 */
	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
     
	/**
	 * Create a new AnnotationConfigApplicationContext, deriving bean definitions
	 * from the given component classes and automatically refreshing the context.
	 * @param componentClasses one or more component classes &mdash; for example,
	 * {@link Configuration @Configuration} classes
	 */
	public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		this();
		register(componentClasses);
		refresh();
	}    
}    

register方法说明

  • 它是根据传入的配置类字节码解析Bean对象中注解的(包括类上和类中方法和字段上的注解。如果类没有加注解,那么类中方法和字段上的注解不会被扫描)。使用的是AnnotatedGenericBeanDefinition,里面包含了BeanDefinition和Scope两部分信息,其中BeanDefinition是传入注解类的信息,即Spring的配置类,Scope是指定Bean的作用范围,默认情况下是单例。
  • 同时,借助AnnotationConfigUtils类的processCommonDefinitionAnnotations方法判断是否使用了Primary、Lazy、DependsOn等注解来决定Bean的加载时机。
  • 在ConfigurationClassBeanDefinitionReader类中的registerBeanDefinitionForImportedConfigurationClass方法会把导入的JdbcConfig类注册到容器中。而loadBeanDefinitionsForBeanMethod方法会解析Bean注解,把被Bean注解修饰的方法返回值存入容器。

5.3.2 使用包扫描的构造函数

构造函数源码

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

	private final AnnotatedBeanDefinitionReader reader;

	private final ClassPathBeanDefinitionScanner scanner;


	/**
	 * Create a new AnnotationConfigApplicationContext that needs to be populated
	 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
	 */
	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
 	/**
	 * Create a new AnnotationConfigApplicationContext, scanning for components
	 * in the given packages, registering bean definitions for those components,
	 * and automatically refreshing the context.
	 * @param basePackages the packages to scan for component classes
	 */
	public AnnotationConfigApplicationContext(String... basePackages) {
		this();
		scan(basePackages);
		refresh();
	}   
}

Scan方法说明

  • 它是根据传入的类路径下(classpath*)的包解析Bean对象中注解的(包括类上以及类成员的),使用
    的是ClassPathBeanDefinitionScanner类中的doScan方法,该方法最终将得到的
    BeanDefinitionHolder信息存储到LinkedHashSet中,为后面初始化容器做准备。
  • doScan中的findCandidateComponents方法调用ClassPathScanningCandidateComponentProvider类中的scanCandidateComponents方法,而此方法又去执行了PathMatchingResourcePatternResolver类中的doFindAllClassPathResources方法,找到指定扫描包的URL(是URL,不是路径。因为是带有file协议的),然后根据磁盘路径读取当前目录及其子目录下的所有类。接下来执行AnnotationConfigUtils类中的
    processCommonDefinitionAnnotations方法。

5.3.3 注册注解类型过滤器

  • ClassPathScanningCandidateComponentProvider的registerDefaultFilters方法说明:
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {	
	
    @SuppressWarnings("unchecked")
	protected void registerDefaultFilters() {
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}
}

5.3.4 准备和初始化容器

  • AbstractApplicationContext类的refresh方法说明:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
    
    	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 1.准备容器,设置一些初始化信息,例如启动时间,验证必要的属性等等。
			prepareRefresh();

			// 2. 告诉子类刷新内部Bean工厂。实际就是重新创建一个Bean工厂。
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// 3.准备使用创建的这个BeanFactory,添加或者注册到当前Bean工厂的一些必要对象
			prepareBeanFactory(beanFactory);

			try {
				// 4.允许子容器对BeanFactory进行后处理。比如:在web环境中Bean的作用范围等。
				postProcessBeanFactory(beanFactory);

				// 5.在singleton的Bean对象初始化前,对Bean工厂进行一些处理
				invokeBeanFactoryPostProcessors(beanFactory);

				// 6.注册拦截Bean创建的处理器
				registerBeanPostProcessors(beanFactory);

				// 7.初始化消息资源接口的实现类。主要用于处理国际化
				initMessageSource();

				// 8.初始化事件
				initApplicationEventMulticaster();

				// 9.AbstractApplicationContext的子类中初始化其他特殊的Bean
				onRefresh();

				// 10.注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean
				registerListeners();

				// 11.实例化所有剩余的(非lazy init)单例。(就是没有被@Lazy修饰的单例Bean)
				finishBeanFactoryInitialization(beanFactory);

				// 12.完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并
//且发布事件(ContextRefreshedEvent)
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				//如果刷新失败那么就会将已经创建好的单例Bean销毁掉
				destroyBeans();

				//重置context的活动状态
				cancelRefresh(ex);

				//抛出异常
				throw ex;
			}

			finally {
				//重置的Spring内核的缓存。因为可能不再需要metadata给单例Bean了。
				resetCommonCaches();
			}
		}
	}

    
}    

5.3.5 实例化和获取Bean对象

  • AbstractBeanFactory的doGetBean方法:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        if (logger.isTraceEnabled()) {
            if (isSingletonCurrentlyInCreation(beanName)) {
                logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                             "' that is not fully initialized yet - a consequence of a circular reference");
            }
            else {
                logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }

    else {
        // Fail if we're already creating this bean instance:
        // We're assumably within a circular reference.
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }

        // Check if bean definition exists in this factory.
        BeanFactory parentBeanFactory = getParentBeanFactory();
        if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
            // Not found -> check parent.
            String nameToLookup = originalBeanName(name);
            if (parentBeanFactory instanceof AbstractBeanFactory) {
                return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                    nameToLookup, requiredType, args, typeCheckOnly);
            }
            else if (args != null) {
                // Delegation to parent with explicit args.
                return (T) parentBeanFactory.getBean(nameToLookup, args);
            }
            else if (requiredType != null) {
                // No args -> delegate to standard getBean method.
                return parentBeanFactory.getBean(nameToLookup, requiredType);
            }
            else {
                return (T) parentBeanFactory.getBean(nameToLookup);
            }
        }

        if (!typeCheckOnly) {
            markBeanAsCreated(beanName);
        }

        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            checkMergedBeanDefinition(mbd, beanName, args);

            // Guarantee initialization of beans that the current bean depends on.
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dep : dependsOn) {
                    if (isDependent(beanName, dep)) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                                        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                    }
                    registerDependentBean(dep, beanName);
                    try {
                        getBean(dep);
                    }
                    catch (NoSuchBeanDefinitionException ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                                        "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                    }
                }
            }

            // Create bean instance.
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    }
                    catch (BeansException ex) {
                        // Explicitly remove instance from singleton cache: It might have been put there
                        // eagerly by the creation process, to allow for circular reference resolution.
                        // Also remove any beans that received a temporary reference to the bean.
                        destroySingleton(beanName);
                        throw ex;
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }

            else if (mbd.isPrototype()) {
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }

            else {
                String scopeName = mbd.getScope();
                final Scope scope = this.scopes.get(scopeName);
                if (scope == null) {
                    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                }
                try {
                    Object scopedInstance = scope.get(beanName, () -> {
                        beforePrototypeCreation(beanName);
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        finally {
                            afterPrototypeCreation(beanName);
                        }
                    });
                    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                }
                catch (IllegalStateException ex) {
                    throw new BeanCreationException(beanName,
                                                    "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                                    "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                                    ex);
                }
            }
        }
        catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }

5.4 BeanNameGenerator及其实现类

5.4.1 BeanNameGenerator

  • BeanNameGenerator.java
public interface BeanNameGenerator {

	/**
	 * Generate a bean name for the given bean definition.
	 * @param definition the bean definition to generate a name for
	 * @param registry the bean definition registry that the given definition
	 * is supposed to be registered with
	 * @return the generated bean name
	 */
	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}
  • 它有两个实现类,分别是:

BeanNameGenerator的实现类

  • 其中DefaultBeanNameGenerator是给资源文件加载Bean时使用,AnnotationBeanNameGenerator是为了处理注解生成Bean name的情况。

5.4.2 AnnotationBeanNameGenerator

  • AnnotationBeanNameGenerator.java
/**
* A convenient constant for a default {@code AnnotationBeanNameGenerator} instance,
* as used for component scanning purposes.
* @since 5.2
*/
public class AnnotationBeanNameGenerator implements BeanNameGenerator {

	
	public static final AnnotationBeanNameGenerator INSTANCE = new AnnotationBeanNameGenerator();

	private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";

	private final Map<String, Set<String>> metaAnnotationTypesCache = new ConcurrentHashMap<>();

	/**
    * 此方法是接口中抽象方法的实现
    * 该方法分为两个部分:
    * 第一个部分:当指定了Bean的名称,则直接使用指定的名称。
    * 第二个部分:如果没有指定Bean的名称,则使用注解Bean名称的命名规则,生成Bean的唯一标识
    * 
    */
	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        //判断Bean的定义信息是否是基于注解的
		if (definition instanceof AnnotatedBeanDefinition) {
            //解析注解中的属性,看看有没有指定的Bean的唯一标识
			String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
			if (StringUtils.hasText(beanName)) {
				// 返回直接的属性指定的Bean的唯一标识
				return beanName;
			}
		}
		// 调用方法,使用注解Bean名称的命名规则,生成Bean的唯一标识
		return buildDefaultBeanName(definition, registry);
	}

	/**
	 * Derive a bean name from one of the annotations on the class.
	 * @param annotatedDef the annotation-aware bean definition
	 * @return the bean name, or {@code null} if none is found
	 */
	@Nullable
	protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
		AnnotationMetadata amd = annotatedDef.getMetadata();
		Set<String> types = amd.getAnnotationTypes();
		String beanName = null;
		for (String type : types) {
			AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
			if (attributes != null) {
				Set<String> metaTypes = this.metaAnnotationTypesCache.computeIfAbsent(type, key -> {
					Set<String> result = amd.getMetaAnnotationTypes(key);
					return (result.isEmpty() ? Collections.emptySet() : result);
				});
				if (isStereotypeWithNameValue(type, metaTypes, attributes)) {
					Object value = attributes.get("value");
					if (value instanceof String) {
						String strVal = (String) value;
						if (StringUtils.hasLength(strVal)) {
							if (beanName != null && !strVal.equals(beanName)) {
								throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
										"component names: '" + beanName + "' versus '" + strVal + "'");
							}
							beanName = strVal;
						}
					}
				}
			}
		}
		return beanName;
	}
}    

5.4.3 DefaultBeanNameGenerator

  • DefaultBeanNameGenerator.java
public class DefaultBeanNameGenerator implements BeanNameGenerator {

	/**
	 * A convenient constant for a default {@code DefaultBeanNameGenerator} instance,
	 * as used for {@link AbstractBeanDefinitionReader} setup.
	 * @since 5.2
	 */
	public static final DefaultBeanNameGenerator INSTANCE = new DefaultBeanNameGenerator();


	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
	}

}

5.5 ScopeProxyMode枚举

public enum ScopedProxyMode {

	/**
	 * Default typically equals {@link #NO}, unless a different default
	 * has been configured at the component-scan instruction level.
	 */
	DEFAULT,

	/**
	 * Do not create a scoped proxy.
	 * <p>This proxy-mode is not typically useful when used with a
	 * non-singleton scoped instance, which should favor the use of the
	 * {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it
	 * is to be used as a dependency.
	 */
	NO,

	/**
	 * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by
	 * the class of the target object.
	 */
	INTERFACES,

	/**
	 * Create a class-based proxy (uses CGLIB).
	 */
	TARGET_CLASS

}

5.6 自定义组件扫描过滤规则

5.6.1 FilterType枚举

public enum FilterType {

	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,

	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,

	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,

	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,

	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM
}

5.6.2 TypeFilter接口

@FunctionalInterface
public interface TypeFilter {

	/**
	 * Determine whether this filter matches for the class described by
	 * the given metadata.
	 * @param metadataReader the metadata reader for the target class
	 * @param metadataReaderFactory a factory for obtaining metadata readers
	 * for other classes (such as superclasses and interfaces)
	 * @return whether this filter matches
	 * @throws IOException in case of I/O failure when reading metadata
	 */
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;

}

5.6.3 使用Spring提供的过滤规则--AnnotationTypeFilter

package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Service;


/**
 * 标注这是一个注解配置类
 */
@Configuration
@ComponentScan(basePackages = "com.sunxiaping", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Service.class)})
public class SpringConfig {
	
}

5.6.4 自定义过滤规则

5.6.4.1 场景分析

  • 在实际开发中,有很多下面这种业务场景:一个业务需求根据环境的不同可能有很多种实现。针对不同的环境,需要加载不同的实现。请看下面的案例。

  • 我们现在是一个汽车销售集团,在成立之初,只是在北京销售汽车,我们的项目研发完成后只在北京部署上线。但是随着公司业务的发展,现在全国各地均有销售大区,总部设在北京。各大区有独立的项目部署,但是每个大区的业绩计算和绩效提成的计算方式并不相同。

  • 例如:

    • 在华北地区销售一套豪华级轿车绩效算5,提成是销售额1%;销售豪华级SUV绩效算3,提成是0.5%。
    • 在西南地区销售一套豪华级轿车绩效算3,提成是销售额0.5%;销售豪华级SUV绩效算5,提成是1.5%。
  • 这时,我们如果针对不同大区对项目源码进行删减替换,会带来很多不必要的麻烦。而如果加入一些if/else的判断,显得过于简单粗暴。此时应该考虑采用桥接设计模式,把涉及到区域性差异的模块功能单独抽取到代表区域功能的接口中。针对不同区域进行实现。并且在扫描组件注册到容器中的时候,采用哪个区域的具体实现,应该采用配置文件配置起来。而自定义TypeFilter就可以实现注册指定区域的组件到容器中。

5.6.4.2 代码实现

  • 新建用于区域的注解District.java
package com.sunxiaping.spring5.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用于定义区域的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface District {
    /**
     * 用于指定区域名称
     *
     * @return
     */
    String value();

}
  • 新建属性文件district.properties
common.district.name=north
  • 新建绩效计算的桥接接口DistrictPerformance.java
package com.sunxiaping.spring5.service;

/**
 * 绩效计算的桥接接口
 */
public interface DistrictPerformance {

    /**
     * 根据不同车辆类型计算绩效
     *
     * @param type CAR SUV
     */
    void calPerformance(String type);
}
  • 新建销售分成的桥接接口DistrictPercentageService.java
package com.sunxiaping.spring5.service;

/**
 * 销售分成的桥接接口
 */
public interface DistrictPercentageService {

    /**
     * 不同车辆类型的提成不同
     *
     * @param type CAR SUV
     */
    void salePercentage(String type);

}
  • 新建华北地区的销售提成具体实现NorthDistrictPercentageServiceImpl.java
package com.sunxiaping.spring5.service.impl.north;

import com.sunxiaping.spring5.annotations.District;
import com.sunxiaping.spring5.service.DistrictPercentageService;
import org.springframework.stereotype.Service;

/**
 * 华北地区的销售提成具体实现
 */
@Service("districtPercentage")
@District("north")
public class NorthDistrictPercentageServiceImpl implements DistrictPercentageService {

    @Override
    public void salePercentage(String type) {
        if ("SUV".equalsIgnoreCase(type)) {
            System.out.println("华北地区" + type + "提成是0.5%");
        } else if ("CAR".equalsIgnoreCase(type)) {
            System.out.println("华北地区" + type + "提成是1%");
        }
    }
}
  • 新建华北地区的绩效计算NorthDistrictPerformanceServiceImpl.java
package com.sunxiaping.spring5.service.impl.north;

import com.sunxiaping.spring5.annotations.District;
import com.sunxiaping.spring5.service.DistrictPerformance;
import org.springframework.stereotype.Service;

/**
 * 华北地区的绩效计算
 */
@Service("districtPerformance")
@District("north")
public class NorthDistrictPerformanceServiceImpl implements DistrictPerformance {
    @Override
    public void calPerformance(String type) {
        if ("SUV".equalsIgnoreCase(type)) {
            System.out.println("华北地区" + type + "绩效是3");
        } else if ("CAR".equalsIgnoreCase(type)) {
            System.out.println("华北地区" + type + "绩效是5");
        }
    }
}
  • 新建西南地区的销售提成具体实现SouthwestDistrictPercentageServiceImpl.java
package com.sunxiaping.spring5.service.impl.southwest;

import com.sunxiaping.spring5.annotations.District;
import com.sunxiaping.spring5.service.DistrictPercentageService;
import org.springframework.stereotype.Service;

/**
 * 西南地区的销售提成具体实现
 */
@Service("districtPercentage")
@District("southwest")
public class SouthwestDistrictPercentageServiceImpl implements DistrictPercentageService {

    @Override
    public void salePercentage(String type) {
        if ("SUV".equalsIgnoreCase(type)) {
            System.out.println("西南地区" + type + "提成是1.5%");
        } else if ("CAR".equalsIgnoreCase(type)) {
            System.out.println("西南地区" + type + "提成是0.5%");
        }
    }
}
  • 新建西南地区的绩效计算SouthwestDistrictPerformanceServiceImpl.java
package com.sunxiaping.spring5.service.impl.southwest;

import com.sunxiaping.spring5.annotations.District;
import com.sunxiaping.spring5.service.DistrictPerformance;
import org.springframework.stereotype.Service;

/**
 * 西南地区的绩效计算
 */
@Service("districtPerformance")
@District("southwest")
public class SouthwestDistrictPerformanceServiceImpl implements DistrictPerformance {
    @Override
    public void calPerformance(String type) {
        if ("SUV".equalsIgnoreCase(type)) {
            System.out.println("西南地区" + type + "绩效是5");
        } else if ("CAR".equalsIgnoreCase(type)) {
            System.out.println("西南地区" + type + "绩效是3");
        }
    }
}
  • 新建自定义扫描规则过滤器DistrictTypeFilter.java
package com.sunxiaping.spring5.typefilter;

import com.sunxiaping.spring5.annotations.District;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.PathMatcher;

import java.util.Properties;

/**
 * 自定义扫描规则过滤器
 */
public class DistrictTypeFilter extends AbstractTypeHierarchyTraversingFilter {

    //定义路径校验对象
    private PathMatcher pathMatcher;

    //定义区域名称
    //注意:此处的数据,应该是读取配置文件获取的
    //此处不能使用@Value注解读取Properties配置文件的内容。
    private String districtName;


    protected DistrictTypeFilter() {
        /*
         * 调用父类的构造函数
         * 第一个参数false,表示不考虑基类
         * 第二个参数false,表示不考虑接口上的信息
         */
        super(false, false);

        //借助Spring默认的Resource通配符方式
        pathMatcher = new AntPathMatcher();
        //读取配置文件
        try {
            Properties properties = PropertiesLoaderUtils.loadAllProperties("district.properties");
            districtName = properties.getProperty("common.district.name");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 本类将注册为exclude,返回true表示拒绝
     *
     * @param className
     * @return
     */
    @Override
    protected boolean matchClassName(String className) {
        //判断是否在指定包下的类(只处理和区域相关的业务类)
        if (!isPotentialPackageClass(className)) {
            //不符合路径规则
            return false;
        }
        //判断当前区域和配置的区域是否一致,不一致,则不能注册到Spring的IOC容器中
        try {
            Class<?> clazz = ClassUtils.forName(className, DistrictTypeFilter.class.getClassLoader());
            //获取District注解
            District district = clazz.getAnnotation(District.class);
            //判断是否有此注解
            if (null == district) {
                return false;
            }
            //取出注解的属性
            String districtValue = district.value();
            //如果取出的value属性的值和配置文件中提供的值一致,则注册到Spring的IOC容器中,返回true,否则返回false

            return !(districtName.equalsIgnoreCase(districtValue));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    //定义key处理类的类名,指定package下的
    private static final String PATTERN_STANDARD = ClassUtils.convertClassNameToResourcePath("com.sunxiaping.spring5.service.*.**");

    private boolean isPotentialPackageClass(String className) {
        //将类名转换为资源路径,以匹配是否符合扫描条件
        String path = ClassUtils.convertClassNameToResourcePath(className);
        //校验
        return pathMatcher.match(PATTERN_STANDARD, path);
    }


}
  • 新建Spring的配置类SpringConfig.java
package com.sunxiaping.spring5.config;

import com.sunxiaping.spring5.typefilter.DistrictTypeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan(basePackages = "com.sunxiaping.spring5",excludeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes = DistrictTypeFilter.class)})
public class SpringConfig {

}
  • 测试
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import com.sunxiaping.spring5.service.DistrictPercentageService;
import com.sunxiaping.spring5.service.DistrictPerformance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class SpringTest {

    @Resource(name = "districtPerformance")
    private DistrictPerformance districtPerformance;

    @Resource(name = "districtPercentage")
    private DistrictPercentageService districtPercentageService;

    @Test
    public void test() {
        districtPerformance.calPerformance("SUV");
        districtPercentageService.salePercentage("SUV");
    }
}

5.7 @Import注解的高级分析

5.7.1 概述

  • 我们在注入Bean对象的时候,可选的方式很多。
  • 例如:
    • 1️⃣我们自己写的类,可以使用@Component、@Service、@Repository和@Controller注解等。
    • 2️⃣我们导入的第三方库中的类,可以使用@Bean注解(当需要做一些初始化操作时,比如DataSource),也可以使用@Import注解,直接指定要引入的类的字节码。
    • 3️⃣当我们的类很多的时候,在每个类上加注解会很繁琐,同时使用@Bean或@Import写起来也很麻烦。此时我们可以采用自定义ImportSelector或者ImportBeanDefinitionRegistrar来实现。在SpringBoot中,@EnableXXX这样的注解,绝大多数都借助了ImportSelector或者ImportBeanDefinitionRegistrar。@EnableTransactionManager就是借助了ImportSelector,而@EnableAspectJAutoProxy就是借助了ImportBeanDefinitionRegistrar。
  • ImportSelector和ImportBeanDefinitionRegistrar的共同点:
    • 它们都是用于动态注册Bean对象到容器中,并且支持大批量的Bean的导入。
  • ImportSelector和ImportBeanDefinitionRegistrar的区别:
    • ImportSelector是一个接口,我们在使用的时候需要自己提供实现类。实现类中返回要注册的Bean的全限定类名数组,然后执行ConfigurationClassParser类中的processImports方法注册Bean对象。
    • ImportBeanDefinitionRegistrar也是一个接口,需要我们自己编写实现类,在实现类中手动注册Bean到容器中。

注意:

  • 实现了ImportSelector和ImportBeanDefinitionRegistrar接口的类不会被解析成一个Bean注册到容器中。
  • ImportSelector注册到容器中的Bean的唯一标识是全限定类名,而非短类名。
  • ImportBeanDefinitionRegistrar注册到容器中的Bean的唯一标识是短类名,而非全限定类名,当然可以修改为全限定类名。

5.7.2 自定义ImportSelector

  • 新建custom.properties用于定义扫描的AspectJ表达式
custom.importSelector.expression=com.sunxiaping.spring5.service.impl.*
  • 新建UserService.java
package com.sunxiaping.spring5.service;

public interface UserService {

    /**
     * 新增用户信息
     */
    void saveUser();

}
  • 新建UserServiceImpl.java
package com.sunxiaping.spring5.service.impl;

import com.sunxiaping.spring5.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("模拟保存用户信息");
    }
}
  • 自定义ImportSelector
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * 自定义ImportSelector
 */
public class SelfImportSelector implements ImportSelector {

    /**
     * AspectJ表达式
     */
    private String expression;

    /**
     * 默认构造函数,用于读取配置文件,给表达式赋值
     */
    public SelfImportSelector() {
        try {
            Properties properties = PropertiesLoaderUtils.loadAllProperties("custom.properties");
            //给AspectJ表达式赋值
            expression = properties.getProperty("custom.importSelector.expression");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 实现获取要导入类的全限定类名
     * 导入的过滤规则TypeFilter用aspectJ表达式的方式
     *
     * @param importingClassMetadata
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //定义扫描包的名称
        String[] basePackages = null;
        //判断有@Import注解的类上有没有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //取出@ComponentScan注解的属性(basePackages或value)
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //取出basePackages的属性值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //判断是否有@ComponentScan注解,是否指定了包扫描的信息
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //取出@Import注解注释的类所在的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //把包名填充到basePackages里面
            basePackages = new String[]{basePackage};
        }
        //创建类路径扫描器  参数的含义:表明不使用默认的过滤规则
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        //创建类型过滤器(此处使用的是AspectJ表达式类型的过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression, this.getClass().getClassLoader());
        //把类型过滤器添加到类路径扫描器中
        scanner.addIncludeFilter(typeFilter);

        //定义要扫描类的全限定类名集合
        Set<String> classes = new HashSet<>();
        //填充集合的内容
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> {
                classes.add(beanDefinition.getBeanClassName());
            });
        }
        //按照方法的返回值要求,返回全限定类名的数组
        return classes.toArray(new String[classes.size()]);
    }

}
  • Spring的配置类SpringConfig.java
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * 标注这是一个注解配置类
 */
@Configuration
@ComponentScan(basePackages = "com.sunxiaping")
@Import(SelfImportSelector.class)
public class SpringConfig {


}
  • 测试
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import com.sunxiaping.spring5.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Resource(name = "com.sunxiaping.spring5.service.impl.UserServiceImpl")
    private UserService userService;

    @Test
    public void test(){
        userService.saveUser();
    }
}

5.7.3 自定义ImportBeanDefinitionRegistrar

  • 参照5.7.2 自定义ImportSelector 的代码。
  • 自定义ImportBeanDefinitionRegistrar
package com.sunxiaping.spring5.config;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.util.*;

public class SelfImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * AspectJ表达式
     */
    private String expression;

    /**
     * 默认构造函数,用于读取配置文件,给表达式赋值
     */
    public SelfImportBeanDefinitionRegistrar() {
        try {
            Properties properties = PropertiesLoaderUtils.loadAllProperties("custom.properties");
            //给AspectJ表达式赋值
            expression = properties.getProperty("custom.importSelector.expression");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 实现注册Bean的功能,它是通过扫描注定包实现的
     *
     * @param importingClassMetadata
     * @param registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //定义包的集合
        List<String> backPackages = null;
        //判断是否有@Component注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //取出@ComponentScan注解的属性(basePackages或value)
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //取出basePackages的属性值,并设置到包的集合中
            backPackages = new ArrayList<>(Arrays.asList((String[]) annotationAttributes.get("basePackages")));
        }
        //判断没有@Component注解或者有@Component注解但是value值为空
        if (backPackages == null || backPackages.size() == 0) {
            //取出@Import注解注释的类所在的包名
            String basePackage = null;
            try {
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            backPackages = new ArrayList<>();
            backPackages.add(basePackage);
        }

        //创建类路径扫描器  参数的含义:表明不使用默认的过滤规则
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        //创建类型过滤器(此处使用的是AspectJ表达式类型的过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression, this.getClass().getClassLoader());
        //把类型过滤器添加到类路径扫描器中
        scanner.addIncludeFilter(typeFilter);

        //扫描指定的包
        scanner.scan(backPackages.toArray(new String[backPackages.size()]));
    }
}
  • Spring的配置类SpringConfig.java
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * 标注这是一个注解配置类
 */
@Configuration
@ComponentScan(basePackages = "com.sunxiaping")
@Import(SelfImportBeanDefinitionRegistrar.class)
public class SpringConfig {

}
  • 测试:
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import com.sunxiaping.spring5.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Resource
    private UserService userService;

    @Test
    public void test(){
        userService.saveUser();
    }

}

5.7.4 原理分析

  • 我们自定义导入器的解析是写在ConfigurationClassParser类中的processImports方法中。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
                            boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
        return;
    }

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                //对ImportSelector的处理
                if (candidate.isAssignable(ImportSelector.class)) {
                    // Candidate class is an ImportSelector -> delegate to it to determine imports
                    Class<?> candidateClass = candidate.loadClass();
                    ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                                                                   this.environment, this.resourceLoader, this.registry);
                    Predicate<String> selectorFilter = selector.getExclusionFilter();
                    if (selectorFilter != null) {
                        exclusionFilter = exclusionFilter.or(selectorFilter);
                    }
                    if (selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    }
                    else {
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                        processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                    }
                }
                //对ImportBeanDefinitionRegistrar的处理
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // Candidate class is an ImportBeanDefinitionRegistrar ->
                    // delegate to it to register additional bean definitions
                    Class<?> candidateClass = candidate.loadClass();
                    ImportBeanDefinitionRegistrar registrar =
                        ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                                             this.environment, this.resourceLoader, this.registry);
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                }
                else {
                    // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                    // process it as an @Configuration class
                    this.importStack.registerImport(
                        currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                }
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                "Failed to process import candidates for configuration class [" +
                configClass.getMetadata().getClassName() + "]", ex);
        }
        finally {
            this.importStack.pop();
        }
    }
}

5.8 自定义PropertySourceFactory实现YAML文件解析

5.8.1 PropertySourceFactory接口及DefaultPropertySourceFactory实现类

  • PropertySourceFactory.java
public interface PropertySourceFactory {

	/**
	 * Create a {@link PropertySource} that wraps the given resource.
	 * @param name the name of the property source
	 * (can be {@code null} in which case the factory implementation
	 * will have to generate a name based on the given resource)
	 * @param resource the resource (potentially encoded) to wrap
	 * @return the new {@link PropertySource} (never {@code null})
	 * @throws IOException if resource resolution failed
	 */
	PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;

}
  • DefaultPropertySourceFactory.java
public class DefaultPropertySourceFactory implements PropertySourceFactory {

	@Override
	public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
		return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
	}

}

5.8.2 执行过程分析

5.8.2.1 ResourcePropertySource

  • ResourcePropertySource.java
/**
* DefaultPropertySourceFactory在创建PropertySource对象的时候使用的是此类的构造函数
*/
public class ResourcePropertySource extends PropertiesPropertySource {

	/** The original resource name, if different from the given name. */
	@Nullable
	private final String resourceName;


	/**
	 * 当我们没有指定名称的时候,执行的是此构造函数,此构造函数调用的是父类的构造函数
	 * 通过读取父类的构造函数,得知第二个参数是一个properties文件
	 * Spring使用了一个工具类获取properties文件
	 */
	public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
		super(name, PropertiesLoaderUtils.loadProperties(resource));
		this.resourceName = getNameForResource(resource.getResource());
	}
}    

5.8.2.2 PropertiesPropertySource

  • PropertiesPropertySource.java
/**
* 此类是ResourcePropertySource的父类
*/
public class PropertiesPropertySource extends MapPropertySource {
	/**
	* 此构造函数中包含两个参数,第一个是名称,第二个是解析好的properties文件
	*/
	@SuppressWarnings({"rawtypes", "unchecked"})
	public PropertiesPropertySource(String name, Properties source) {
		super(name, (Map) source);
	}

	protected PropertiesPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}
}    

5.8.2.3 PropertiesLoaderUtils

  • PropertiesLoaderUtils.java
/**
* 获取properties的工具类
*/
public abstract class PropertiesLoaderUtils {

	private static final String XML_FILE_EXTENSION = ".xml";


	/**
	 * ResourcePropertySource类的构造函数就是执行了此方法
	 */
	public static Properties loadProperties(EncodedResource resource) throws IOException {
		Properties props = new Properties();
		fillProperties(props, resource);
		return props;
	}
    
    public static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
			throws IOException {

		InputStream stream = null;
		Reader reader = null;
		try {
			String filename = resource.getResource().getFilename();
			if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
				stream = resource.getInputStream();
                  //读取xml文件
				persister.loadFromXml(props, stream);
			}
			else if (resource.requiresReader()) {
				reader = resource.getReader();
                 //读取properties文件
				persister.load(props, reader);
			}
			else {
				stream = resource.getInputStream();
				persister.load(props, stream);
			}
		}
		finally {
			if (stream != null) {
				stream.close();
			}
			if (reader != null) {
				reader.close();
			}
		}
	}
}    

5.8.2.4 PropertiesPersister

  • PropertiesPersister.java
/**
 *  Spring中声明的解析XML和properties文件的接口
 */
public interface PropertiesPersister {

	/**
	 * Load properties from the given InputStream into the given
	 * Properties object.
	 * @param props the Properties object to load into
	 * @param is the InputStream to load from
	 * @throws IOException in case of I/O errors
	 * @see java.util.Properties#load
	 */
	void load(Properties props, InputStream is) throws IOException;

	/**
	 * Load properties from the given Reader into the given
	 * Properties object.
	 * @param props the Properties object to load into
	 * @param reader the Reader to load from
	 * @throws IOException in case of I/O errors
	 */
	void load(Properties props, Reader reader) throws IOException;
}    
  • DefaultPropertiesPersister.java
/**
* PropertiesPersister的实现类
*/
public class DefaultPropertiesPersister implements PropertiesPersister {

	@Override
	public void load(Properties props, InputStream is) throws IOException {
		props.load(is);
	}

	@Override
	public void load(Properties props, Reader reader) throws IOException {
		props.load(reader);
	}
}    

5.8.3 自定义YAMLPropertySourceFactory

5.8.3.1 编写yml配置文件

  • jdbc.yml
# Yet Another Markup Language 另一种标记语言
jdbc:
  url: jdbc:mysql://192.168.2.112:3306/spring5?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
  driver: com.mysql.cj.jdbc.Driver
  user: root
  password: 123456

5.8.3.2 导入yml解析器的坐标

<!--    导入YAML解析工厂坐标    -->
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.26</version>
</dependency>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.23</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <!--    导入YAML解析工厂坐标    -->
    <dependency>
        <groupId>org.yaml</groupId>
        <artifactId>snakeyaml</artifactId>
        <version>1.26</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>11</source>
                <target>11</target>
                <encoding>utf-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>

5.8.3.3 编写自定义的PropertySourceFactory

  • YAMLPropertySourceFactory.java
package com.sunxiaping.spring5.factory;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;
import java.util.Properties;

/**
 * 自定义YAML的PropertySourceFactory
 */
public class YAMLPropertySourceFactory implements PropertySourceFactory {
    /**
     * 返回PropertySource对象
     *
     * @param name
     * @param resource
     * @return
     * @throws IOException
     */
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        //创建YAML文件解析工厂
        YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
        //设置要解析的资源内容
        factoryBean.setResources(resource.getResource());
        //把资源解析为properties文件
        Properties properties = factoryBean.getObject();

        return (name != null ? new PropertiesPropertySource(name, properties) : new PropertiesPropertySource(resource.getResource().getFilename(), properties));
    }
}

5.8.3.4 使用@PropertySource注解的factory属性

  • JdbcConfig.java
package com.sunxiaping.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sunxiaping.spring5.factory.YAMLPropertySourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;


@Configuration
@PropertySource(value = "classpath:jdbc.yml", factory = YAMLPropertySourceFactory.class)
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.user}")
    private String user;

    @Value("${jdbc.password}")
    private String password;


    /**
     * 配置数据源
     *
     * @return 数据源
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

}
  • SpringConfig.java
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * Spring的配置类
 */
@Configuration
@Import(JdbcConfig.class)
public class SpringConfig {

}

5.8.3.5 测试

  • 测试:
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Autowired
    private DataSource dataSource;

    @Test
    public void test() throws SQLException {
        Connection connection = dataSource.getConnection();
        System.out.println("connection = " + connection);
    }

}

5.9 @Profile注解的使用

5.9.1 使用场景分析

  • @Profile注解是Spring提供的一个用来表明当前运行环境的注解。我们正常开发的过程中经常遇到的问题是,开发环境是一套环境,测试环境是一套环境,生产环境是一套环境。这样从开发到测试再到部署,会对程序中的配置修改多次,尤其是从测试到上线的环境,让测试的也不敢保证改了那个配置之后能不能在线上运行。为了解决这个问题,我们一般会使用一种方法,就是针对不同的环境进行不同的额皮质,从而在不同的场景中跑我们的程序。
  • 而Spring中的@Profile注解的作用就体现在这里。在Spring中使用DI进行注入的时候,能够根据当前制定的运行环境来注入相应的Bean。最常见的就是使用不同的DataSource。

5.9.2 代码实现

  • JdbcConfig.java
package com.sunxiaping.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import javax.sql.DataSource;

@Configuration
public class JdbcConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        System.out.println("开发环境下的数据源");
        return new DruidDataSource();
    }

    @Bean
    @Profile("test")
    public DataSource testDataSource() {
        System.out.println("测试环境下的数据源");
        return new DruidDataSource();
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        System.out.println("生产环境下的数据源");
        return new DruidDataSource();
    }

}
  • SpringConfig.java
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * Spring的配置类
 */
@Configuration
@Import(JdbcConfig.class)
public class SpringConfig {
    
}
  • 测试:
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
@ActiveProfiles(value = "prod")
public class Spring5Test {

    @Autowired
    private DataSource dataSource;

    @Test
    public void test(){
        System.out.println(dataSource);
    }
}
posted @ 2020-09-13 09:15  许大仙  阅读(356)  评论(0编辑  收藏  举报