Spring 註解開發

Juno3550發表於2021-12-04


註解開發簡介

註解開發的好處:使用註解的形式替代 xml 配置,將繁雜的 Spring 配置檔案從工程中徹底消除掉,簡化書寫。

image

註解驅動的弊端:

  • 為了達成註解驅動的目的,可能會將原先很簡單的書寫,變得更加複雜。

  • XML 中配置第三方開發的資源是很方便的,但使用註解驅動無法在第三方開發的資源中進行編輯,因此會增大開發工作量。

image


常用註解

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:

```java
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

image

註解整合 MyBatis 的開發步驟

  1. 修改 mybatis 外部配置檔案格式為註解格式;
  2. 業務類使用 @Component 宣告 bean,使用 @Autowired 注入物件;
  3. 建立配置檔案 JDBCConfig 與 MyBatisConfig 類,並將其匯入到核心配置類 SpringConfig;
  4. 開啟註解掃描;
  5. 使用 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;
    }

}
  • 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({DataSourceConfig.class, MybatisConfig.class})  // 相當於 <import resource=""/>
public class SpringConfig {

}

註解整合 Junit

註解整合 Junit 的開發步驟

  1. 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 核心介面

image


元件掃描器:@ComponentScan

元件掃描器:開發過程中,需要根據需求載入必要的 bean 或排除指定 bean。

image

應用場景:

  • 資料層介面測試
  • 業務層介面測試
  • 各種執行環境設定

配置掃描器

  • 名稱:@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 初始化過程解析

image

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 初始化

image

  • 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;
    }
}

相關文章