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️⃣非直接耦合:
- 两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
- 1️⃣内容耦合:
-
为什么要解耦?
- 耦合是影响软件复杂程序和设计质量的一个重要因素,在设计上我们应该采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
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注解的高级分析
。
- value:用于指定其他配置类的字节码。它支持指定多个配置类。关于ImportSelector和ImportBeanDefinitionRegister请查看
- 使用场景:
- 当我们在使用注解驱动开发时,由于配置项过多,如果都写在一个类里面,配置结构和内容将会杂乱不堪,此时使用@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
* — for example, {@code "classpath:/com/myco/app.properties"}
* or {@code "file:/path/to/file.xml"}.
* <p>Resource location wildcards (e.g. **/*.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
注解中是没有encoding
和factory
属性的,所以需要配置资源文件解析器到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注解的使用
)
- 当我们在开发时,可能会使用多个平台来测试,例如我们的测试数据库分别部署到了Linux和Windows两个操作系统上,现在根据我们的工程运行环境连接数据库。此时就可以使用此注解。同时基于此注解引出的@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 — for example, <code>#{systemProperties.myProp}</code>.
*/
String value();
}
说明
- 作用:
- 用于注入基本数据类型和String类型的数据。它支持Spring的EL表达式,可以通过
${}
的方式获取配置文件中的数据。配置文件支持properties、xml和yml格式。
- 用于注入基本数据类型和String类型的数据。它支持Spring的EL表达式,可以通过
- 属性:
- 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的类图
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。
- byName:
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类图
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为主。
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 — 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);
}
- 它有两个实现类,分别是:
- 其中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);
}
}