- 注解開發簡介
- 常用注解
- 啟用注解功能
- bean 定義:@Component、@Controller、@Service、@Repository
- bean 的引用類型屬性注入:@Autowired、@Qualifier
- bean 的引用類型屬性注入:@Inject、@Named、@Resource
- bean 優先級注入:@Primary
- bean 的非引用類型屬性注入:@Value
- bean 的作用域:@Scope
- bean 的生命周期:@PostConstruct、@PreDestroy
- 加載第三方資源:@Bean
- 加載 properties 文件:@PropertySource
- 純注解開發:@Configuration、@ComponentScan
- 導入第三方配置:@Import
- 綜合案例
- 整合第三方技術
- IoC 底層核心原理
注解開發簡介
注解開發的好處:使用注解的形式替代 xml 配置,將繁雜的 Spring 配置文件從工程中徹底消除掉,簡化書寫。
注解驅動的弊端:
-
為了達成注解驅動的目的,可能會將原先很簡單的書寫,變得更加復雜。
-
XML 中配置第三方開發的資源是很方便的,但使用注解驅動無法在第三方開發的資源中進行編輯,因此會增大開發工作量。
常用注解
Spring 原始注解:主要是替代 <Bean> 的配置。
注解 | 說明 |
---|---|
@Component | 使用在類上用於實例化 Bean |
@Controller | 使用在 web 層類上用於實例化 Bean |
@Service | 使用在 service 層類上用於實例化 Bean |
@Repository | 使用在 dao 層類上用於實例化 Bean |
@Autowired | 使用在字段上用於根據類型依賴注入 |
@Qualifier | 結合 @Autowired 一起使用,用於根據名稱進行依賴注入引用類型 |
@Resource | 相當於 @Autowired + @Qualifier,按照名稱進行注入引用類型 |
@Value | 注入普通類型的屬性 |
@Scope | 標注 Bean 的作用范圍 |
@PostConstruct | 使用在方法上標注該方法是 Bean 的初始化方法 |
@PreDestroy | 使用在方法上標注該方法是 Bean 的銷毀方法 |
Spring 新注解:
使用上面的注解還不能全部替代 xml 配置文件,還需要使用注解替代的配置如下:
- 非自定義的 bean 的配置:<bean>
- 加載 properties 文件的配置:<context:property-placeholder>
- 組件掃描的配置:<context:component-scan>
- 引入其他文件:<import>
注解 | 說明 |
---|---|
@Configuration | 用於指定當前類是一個 Spring 配置類,當創建容器時會從該類上加載注解。用於指定 Spring 在初始化容器時要掃描的包。 |
@ComponentScan | 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package="com.itheima"/> 一樣。 |
@Bean | 使用在方法上,標注將該方法的返回值存儲到 Spring 容器中。 |
@PropertySource | 用於加載 .properties 文件中的配置。 |
@Import | 用於導入其他配置類。 |
啟用注解功能
- 啟動注解掃描,加載類中配置的注解項:
<context:component-scan base-package="packageName"/>
-
說明:
-
在進行包所掃描時,會對配置的包及其子包中所有文件進行掃描。
-
掃描過程是以文件夾遞歸迭代的形式進行的。
-
掃描過程僅讀取合法的 java 文件。
-
掃描時僅讀取 spring 可識別的注解。
-
掃描結束后會將可識別的有效注解轉化為 spring 對應的資源加入 IoC 容器。
-
-
注意:
-
無論是注解格式還是 XML 配置格式,最終都是將資源加載到 IoC 容器中,差別僅僅是數據讀取方式不同。
-
從加載效率上來說,注解優於 XML 配置文件。
-
bean 定義:@Component、@Controller、@Service、@Repository
-
類型:類注解
-
位置:類定義上方。
-
作用:設置該類為 spring 管理的 bean 。
-
示例:
@Component
public class ClassName{}
-
說明:@Controller、@Service 、@Repository 是 @Component 的衍生注解,功能同 @Component 。
-
相關屬性:
- value(默認) :定義 bean 的訪問 id 。
bean 的引用類型屬性注入:@Autowired、@Qualifier
-
類型:屬性注解、方法注解
-
位置:屬性定義上方,方法定義上方。
-
作用:設置對應屬性的對象或對方法進行引用類型傳參。
-
說明:@Autowired 默認按類型裝配,指定 @Qualifier 后則可以指定裝配的 bean 的 id 。
-
相關屬性:
- required:定義該屬性是否允許為 null 。
bean 的引用類型屬性注入:@Inject、@Named、@Resource
-
說明:
- @Inject 與 @Named 是 JSR330 規范中的注解,功能與 @Autowired 和 @Qualifier 完全相同,適用於不同架構場景。
- @Resource 是 JSR250 規范中的注解,可以簡化書寫格式。
-
@Resource 相關屬性:
-
name:設置注入的 bean 的 id 。
-
type:設置注入的 bean 的類型,接收的參數為 Class 類型。
-
bean 優先級注入:@Primary
-
類型:類注解
-
位置:類定義上方。
-
作用:設置類對應的 bean 按類型裝配時優先裝配。
-
說明:@Autowired 默認按類型裝配,當出現相同類型的 bean,使用 @Primary 會提高按類型自動裝配的優先級,但多個 @Primary 會導致優先級設置無效。
bean 的非引用類型屬性注入:@Value
-
類型:屬性注解、方法注解
-
位置:屬性定義上方,方法定義上方。
-
作用:設置對應屬性的值或對方法進行傳參。
-
說明:
-
value 值僅支持非引用類型數據,賦值時對方法的所有參數全部賦值。
-
value 值支持讀取 properties 文件中的屬性值,通過類屬性將 properties 中數據傳入類中。
-
value 值支持 SpEL 。
-
@value 注解如果添加在屬性上方,可以省略 set 方法(set 方法的目的是為屬性賦值)。
-
bean 的作用域:@Scope
-
類型:類注解
-
位置:類定義上方。
-
作用:設置該類作為 bean 對應的 scope 屬性。
-
相關屬性
- value(默認):定義 bean 的作用域,默認為 singleton 。
bean 的生命周期:@PostConstruct、@PreDestroy
-
類型:方法注解
-
位置:方法定義上方。
-
作用:設置該類作為 bean 對應的生命周期方法。
加載第三方資源:@Bean
-
類型:方法注解
-
位置:方法定義上方。
-
作用:設置該方法的返回值作為 spring 管理的 bean 。
-
范例:
@Bean("dataSource")
public DruidDataSource createDataSource() { return ……; }
-
說明:
-
因為第三方 bean 無法在其源碼上進行修改,因此可以使用 @Bean 解決第三方 bean 的引入問題。
-
該注解用於替代 XML 配置中的靜態工廠與實例工廠創建 bean,不區分方法是否為靜態或非靜態。
-
@Bean 所在的類必須被 spring 掃描加載,否則該注解無法生效。
-
-
相關屬性
- value(默認):定義 bean 的訪問 id 。
加載 properties 文件:@PropertySource
-
類型:類注解
-
位置:類定義上方。
-
作用:加載 properties 文件中的屬性值。
-
范例:
@PropertySource(value="classpath:jdbc.properties")
public class ClassName {
@Value("${propertiesAttributeName}")
private String attributeName;
}
-
說明:不支持*通配格式,一旦加載,所有 spring 控制的 bean 中均可使用對應屬性值
-
相關屬性
-
value(默認):設置加載的 properties 文件名。
-
ignoreResourceNotFound:如果資源未找到,是否忽略,默認為 false 。
-
純注解開發:@Configuration、@ComponentScan
-
類型:類注解
-
位置:類定義上方。
-
作用:設置當前類為 spring 核心配置加載類(不再需要 spring 配置文件)。
-
范例:
@Configuration
@ComponentScan("scanPackageName")
public class SpringConfigClassName{
}
-
說明:
-
核心配合類用於替換 spring 核心配置文件,此類可以設置空的,不設置變量與屬性。
-
bean 掃描工作使用注解 @ComponentScan 替代。
-
-
加載純注解格式上下文對象,需要使用 AnnotationConfigApplicationContext:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
導入第三方配置:@Import
-
類型:類注解
-
位置:類定義上方。
-
作用:導入第三方 bean 作為 spring 控制的資源。
-
范例:
@Configuration
@Import(OtherClassName.class)
public class ClassName {
}
-
說明:
-
@Import 注解在同一個類上,僅允許添加一次,如果需要導入多個,使用數組的形式進行設定。
-
在被導入的類中可以繼續使用 @Import 導入其他資源(了解)。
-
@Bean 所在的類可以使用導入的形式進入 spring 容器,無需聲明為 bean 。
-
綜合案例
maven 依賴:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
</dependencies>
spring 配置類
DataSourceConfig.java:
package com.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.PropertySource;
// 數據源配置類
// 相當於 <context:property-placeholder location="classpath:jdbc.properties"/>,且不能用通配符*
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driver}")
private static String driver;
@Value("${jdbc.url}")
private static String url;
@Value("${jdbc.username}")
private static String username;
@Value("${jdbc.password}")
private static String password;
@Bean("dataSource") // 將方法的返回值放置Spring容器中
public static DruidDataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
SpringConfig.java:
package com.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
// Spring核心配置類
@Configuration
@ComponentScan("com") // 相當於 <context:component-scan base-package="com"/>
@Import({DataSourceConfig.class}) // 相當於 <import resource=""/>
public class SpringConfig {
}
dao 層
UserDaoImpl.java:
package com.dao.impl;
import com.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
// 相當於 <bean id="UserDao" ref="com.dao.impl.UserDaoImpl"/>
// @Component("userDao")
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDao save...");
}
}
service 層
UserServiceImpl.java:
package com.service.impl;
import com.dao.UserDao;
import com.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.sql.DataSource;
// 相當於 <bean id="UserService" ref="com.service.impl.UserServiceImpl"/>
// @Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {
// <property name="userDao" ref="userDao"></property>
// @Autowired // 可單獨使用,按照數據類型從spring容器中進行匹配的(有多個相同數據類型的bean時則會有匹配問題)
// @Qualifier("userDao") // 指定bean的id從spring容器中匹配,且要結合@Autowired一起用
@Resource(name="userDao") // 相當於 @Autowired+@Autowired
UserDao userDao;
@Resource(name="dataSource")
DataSource dataSource;
@Value("${jdbc.driver}") // 讀取配置文件中的值
private String driver;
/* 使用注解開發可以省略set方法,使用配置文件則不能省略
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
*/
@Override
public void save() {
System.out.println("driver: "+driver);
System.out.println("dataSource: "+dataSource);
userDao.save();
}
@PostConstruct
public void init() {
System.out.println("service對象的初始化方法");
}
@PreDestroy
public void destroy() {
System.out.println("service對象的銷毀方法");
}
}
controller 層
App.java:
package com.controller;
import com.config.SpringConfig;
import com.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = (UserService)context.getBean("userService");
userService.save();
context.close();
}
}
運行結果:
service對象的初始化方法
dataSource: {
CreateTime:"2021-12-03 01:05:00",
ActiveCount:0,
PoolingCount:0,
CreateCount:0,
DestroyCount:0,
CloseCount:0,
ConnectCount:0,
Connections:[
]
}
UserDao save...
service對象的銷毀方法
整合第三方技術
注解整合 Mybatis
注解整合 MyBatis 的開發步驟:
- 修改 mybatis 外部配置文件格式為注解格式;
- 業務類使用 @Component 聲明 bean,使用 @Autowired 注入對象;
- 建立配置文件 JDBCConfig 與 MyBatisConfig 類,並將其導入到核心配置類 SpringConfig;
- 開啟注解掃描;
- 使用 AnnotationConfigApplicationContext 對象加載配置項。
核心內容如下:
- Maven 依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
- MybatisConfig.java(Mybatis 配置類):
package com.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
/*
<!-- spring整合mybatis后,創建連接用的對象 -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.domain"/>
</bean>
<!-- 掃描mybatis映射配置,將其作為spring的bean進行管理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dao"/>
</bean>
*/
// 以下注解代替以上配置文件內容:返回值會作為Spring容器的bean
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.domain");
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.dao");
return mapperScannerConfigurer;
}
}
- JdbcConfig.java(數據源配置類):
package com.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- SpringConfig.java(Spring 核心配置類):
package com.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com") // 相當於 <context:component-scan base-package="com"/>
@Import({JdbcConfig.class, MybatisConfig.class}) // 相當於 <import resource=""/>
public class SpringConfig {
}
注解整合 Junit
注解整合 Junit 的開發步驟:
- Spring 接管 Junit 的運行權,使用 Spring 專用的 Junit 類加載器;
2.為 Junit 測試用例設定對應的 Spring 容器:
-
從 Spring 5.0 以后,要求 Junit 的版本必須是 4.12 或以上。
-
Junit 僅用於單元測試,不能將 Junit 的測試類配置成 Spring 的 bean,否則該配置將會被打包進入工程中。
示例:整合 Junit5
- Maven 依賴:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
- 測試類:
package com.service;
import com.config.SpringConfig;
import com.domain.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
// 設定spring專用的類加載器
@ExtendWith(SpringExtension.class)
// 設定加載的spring上下文對應的配置
@ContextConfiguration(classes=SpringConfig.class)
public class UserServiceTest {
@Autowired
UserService userService;
@Test
public void testFindById() {
User user = userService.findById(1);
// System.out.println(user);
Assertions.assertEquals("Mick", user.getName());
}
@Test
public void testFindAll() {
List<User> users = userService.findAll();
Assertions.assertEquals(3, users.size());
}
}
IoC 底層核心原理
IoC 核心接口
組件掃描器:@ComponentScan
組件掃描器:開發過程中,需要根據需求加載必要的 bean 或排除指定 bean。
應用場景:
- 數據層接口測試
- 業務層接口測試
- 各種運行環境設置
配置掃描器
-
名稱:@ComponentScan
-
類型:類注解
-
位置:類定義上方
-
作用:設置 spring 配置加載類掃描規則
-
范例:
@Configuration
@ComponentScan(
value="com", // 設置基礎掃描路徑
excludeFilters = // 設置過濾規則,當前為排除過濾
@ComponentScan.Filter( // 設置過濾器
type= FilterType.ANNOTATION, // 設置過濾方式為按照注解進行過濾
classes=Repository.class) // 設置具體的過濾項。如不加載所有@Repository修飾的bean
)
public class SpringConfig {
}
-
includeFilters:設置包含性過濾器
-
excludeFilters:設置排除性過濾器
-
type:設置過濾器類型(過濾策略)
- ANNOTATION
- ASSIGNABLE_TYPE
- ASPECTJ
- REGEX
- CUSTOM
自定義掃描器
-
名稱:TypeFilter
-
類型:接口
-
作用:自定義類型過濾器
示例:
- 自定義掃描器
public class MyTypeFilter implements TypeFilter {
public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException {
ClassMetadata cm = metadataReader.getClassMetadata();
tring className = cm.getClassName();
if(className.equals("com.itheima.dao.impl.BookDaoImpl")){
return true; // 進行過濾(攔截)
}
return false; // 不過濾(放行)
}
}
- 配置類:
@Configuration
@ComponentScan(
value = "com",
excludeFilters = @ComponentScan.Filter(
type= FilterType.CUSTOM,
classes = MyTypeFilter.class
)
)
public class SpringConfig {
}
自定義導入器:ImportSelector
bean 只有通過配置才可以進入 spring 容器,被 spring 加載並控制。配置 bean 的方式如下:
-
XML 文件中使用 <bean/> 標簽配置
-
使用 @Component 及衍生注解配置
企業開發過程中,通常需要配置大量的 bean,因此需要一種快速高效配置大量 bean 的方式。
ImportSelector 注解:
-
類型:接口
-
作用:自定義 bean 導入器(導入未加 @Component 注解的 bean)
示例:
- 自定義導入器:
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata icm) {
// 返回需要導入的bean數組。該bean即使沒加@Component注解也能被掃描識別
return new String[]{"com.dao.impl.AccountDaoImpl"};
}
}
- 配置類:
@Configuration
@ComponentScan("com")
@Import(MyImportSelector.class) // 導入自定義導入器
public class SpringConfig {
}
自定義導入器的封裝工具類:
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.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class CustomerImportSelector implements ImportSelector {
private String expression;
public CustomerImportSelector(){
try {
//初始化時指定加載的properties文件名
Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
//設定加載的屬性名
expression = loadAllProperties.getProperty("path");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//1.定義掃描包的名稱
String[] basePackages = null;
//2.判斷有@Import注解的類上是否有@ComponentScan注解
if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
//3.取出@ComponentScan注解的屬性
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
//4.取出屬性名稱為basePackages屬性的值
basePackages = (String[]) annotationAttributes.get("basePackages");
}
//5.判斷是否有此屬性(如果沒有ComponentScan注解則屬性值為null,如果有ComponentScan注解,則basePackages默認為空數組)
if (basePackages == null || basePackages.length == 0) {
String basePackage = null;
try {
//6.取出包含@Import注解類的包名
basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//7.存入數組中
basePackages = new String[] {basePackage};
}
//8.創建類路徑掃描器
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
//9.創建類型過濾器(此處使用切入點表達式類型過濾器)
TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
//10.給掃描器加入類型過濾器
scanner.addIncludeFilter(typeFilter);
//11.創建存放全限定類名的集合
Set<String> classes = new HashSet<>();
//12.填充集合數據
for (String basePackage : basePackages) {
scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
}
//13.按照規則返回
return classes.toArray(new String[classes.size()]);
}
}
自定義注冊器:ImportBeanDefinitionRegistrar
-
類型:接口
-
作用:自定義 bean 定義注冊器(識別標記了 @Component 的 bean)
示例:
- 自定義注冊器:
// 表示com目錄下的bean全部注冊
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata icm, BeanDefinitionRegistry r) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(r, false);
TypeFilter tf = new TypeFilter() {
public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException {
return true;
}
};
scanner.addIncludeFilter(tf); // 包含
// scanner.addExcludeFilter(tf); // 排除
scanner.scan("com");
}
}
- 配置類:
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class) // 作用等同於 @ComponentScan("com")
public class SpringConfig {
}
封裝工具類:
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.io.IOException;
import java.util.Map;
import java.util.Properties;
public class CustomeImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
private String expression;
public CustomeImportBeanDefinitionRegistrar(){
try {
//初始化時指定加載的properties文件名
Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
//設定加載的屬性名
expression = loadAllProperties.getProperty("path");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//1.定義掃描包的名稱
String[] basePackages = null;
//2.判斷有@Import注解的類上是否有@ComponentScan注解
if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
//3.取出@ComponentScan注解的屬性
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
//4.取出屬性名稱為basePackages屬性的值
basePackages = (String[]) annotationAttributes.get("basePackages");
}
//5.判斷是否有此屬性(如果沒有ComponentScan注解則屬性值為null,如果有ComponentScan注解,則basePackages默認為空數組)
if (basePackages == null || basePackages.length == 0) {
String basePackage = null;
try {
//6.取出包含@Import注解類的包名
basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//7.存入數組中
basePackages = new String[] {basePackage};
}
//8.創建類路徑掃描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
//9.創建類型過濾器(此處使用切入點表達式類型過濾器)
TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
//10.給掃描器加入類型過濾器
scanner.addIncludeFilter(typeFilter);
//11.掃描指定包
scanner.scan(basePackages);
}
}
bean 初始化過程解析
bean 統一初始化
-
BeanFactoryPostProcessor
-
作用:定義了在 bean 工廠對象創建后,bean 對象創建前執行的動作,用於對工廠進行創建后業務處理。
-
運行時機:當前操作用於對工廠進行處理,僅運行一次。
-
-
BeanPostProcessor
-
作用:定義了所有 bean 初始化前后進行的統一動作,用於對 bean 進行創建前業務處理與創建后業務處理。
-
運行時機:當前操作伴隨着每個 bean 的創建過程,每次創建 bean 均運行該操作。
-
-
InitializingBean
-
作用:定義了每個 bean 的初始化前進行的動作,屬於非統一性動作,用於對 bean 進行創建前業務處理。
-
運行時機:當前操作伴隨着任意一個 bean 的創建過程,保障其個性化業務處理。
-
-
注意:上述操作均需要被 spring 容器加載方可運行。
示例:
- BeanFactoryPostProcessor:
package com.post;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
public class MyBeanFactory implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("Bean工廠制作好了");
}
}
- BeanPostProcessor:
package com.post;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBean implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean之前巴拉巴拉");
System.out.println(beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean之后巴拉巴拉");
return bean;
}
}
- InitializingBean:
package com.service.impl;
import com.dao.UserDao;
import com.domain.User;
import com.service.UserService;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements InitializingBean {
// 定義當前bean初始化操作,功效等同於init-method屬性配置
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("UserServiceImpl......bean ...init......");
}
}
- 運行結果:
Bean工廠制作好了
bean之前巴拉巴拉
springConfig
bean之后巴拉巴拉
bean之前巴拉巴拉
com.config.DataSourceConfig
bean之后巴拉巴拉
bean之前巴拉巴拉
dataSource
bean之后巴拉巴拉
bean之前巴拉巴拉
getSqlSessionFactoryBean
bean之后巴拉巴拉
bean之后巴拉巴拉
bean之前巴拉巴拉
userDao
bean之后巴拉巴拉
bean之后巴拉巴拉
bean之前巴拉巴拉
userService
UserServiceImpl......bean ...init......
bean之后巴拉巴拉
bean之前巴拉巴拉
com.service.UserServiceTest.ORIGINAL
bean之后巴拉巴拉
單個 bean 初始化
- FactoryBean:對單一的 bean 的初始化過程進行封裝,達到簡化配置的目的。
FactoryBean 與 BeanFactory 區別:
-
FactoryBean:封裝單個 bean 的創建過程。通常是為了創建另一個 bean 而做的准備工作。
-
BeanFactory:Spring 容器頂層接口,統一定義了 bean 相關的獲取操作。
示例:
import org.springframework.beans.factory.FactoryBean;
public class UserServiceImplFactoryBean implements FactoryBean {
// 重點:返回數據
@Override
public Object getObject() throws Exception {
return new UserServiceImpl();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}