天天看點

spring-基于注解的IoC

基于注解的IoC

1. 快速入門

需求描述

  • 有dao層:

    UserDao

    UserDaoImpl

  • 有service層:

    UserService

    UserServiceImpl

  • 使用注解配置bean,并注入依賴

需求分析

  1. 準備工作:建立Maven項目,導入依賴坐标
  2. 編寫代碼并注解配置:

    編寫dao層、service層代碼,使用注解

    @Component

    配置bean:代替xml裡的

    bean

    标簽

    使用注解

    @Autowired

    依賴注入:代替xml裡的

    property

    constructor-arg

    标簽
  3. 在配置檔案中開啟元件掃描(掃描注解)
  4. 測試

需求實作

1) 準備工作
  • 建立Maven項目,導入依賴坐标
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
           
2) 編寫代碼,并注解配置
  • UserDao

    接口
public interface UserDao {
    void save();
}
           
  • UserDaoImpl

    實作類
@Component("userDao")//等價于@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("save......");
    }
}
           
  • UserService

    接口
public interface UserService {
    void save();
}
           
  • UserServiceImpl

    實作類
@Component("userService")//等價于@Service("userService")
public class UserServiceImpl implements UserService {

    //注意:使用注解配置依賴注入時,不需要再有set方法
    @Autowired
    //@Qualifier("userDao")
    private UserDao userDao;

    @Override
    public void save() {
        userDao.save();
    }
}
           
3) 開啟元件掃描(掃描注解)
  • 建立

    applicationContext.xml

    ,注意引入的

    context

    名稱空間
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--開啟元件掃描-->
    <context:component-scan base-package="com.itheima"/>
</beans>
           
4) 功能測試
  • 建立一個測試類,調用Service
public class UserTest {

    @Test
    public void save(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}
           

步驟小結

  1. 導入jar包:

    spring-context

  2. 編寫service和dao
    • UserServiceImpl

      要注冊bean,就在類上加注解

      @Component

      • 裡邊有一個依賴項要注入:直接在依賴項成員變量上加

        @Autowired

    • UserDaoImpl

      要注冊bean,就在類上加注解

      @Component

  3. 在xml裡開啟元件掃描

2. 注解使用詳解

開啟元件掃描

  • 在Spring中,如果要使用注解開發,就需要在

    applicationContext.xml

    中開啟元件掃描,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 注意:必須要引入context名稱空間之後,才可以使用這個标簽 -->
    <!-- 開啟元件掃描,讓Spring容器掃描com.itheima包下的注解 -->
    <context:component-scan base-package="com.itheima"/>
    
</beans>
           

聲明bean的注解

簡介
注解 說明

@Component

用在類上,相當于bean标簽

@Controller

用在web層類上,配置一個bean(是

@Component

的衍生注解)

@Service

用在service層類上,配置一個bean(是

@Component

的衍生注解)

@Repository

用在dao層類上,配置一個bean(是

@Component

的衍生注解)
  • @Component

    :類級别的一個注解,用于聲明一個bean,使用不多
    • value

      屬性:bean的唯一辨別。如果不配置,預設以首字母小寫的類名為id
  • @Controller, @Service, @Repository

    ,作用和

    @Component

    完全一樣,但更加的語義化,使用更多
    • @Controller

      :用于web層的bean
    • @Service

      :用于Service層的bean
    • @Repository

      :用于dao層的bean
示例
  • UserDaoImpl

    類上使用注解

    @Repository

@Repository("userDao")
public class UserDaoImpl implements UserDao{
}
           
  • UserServiceImpl

    類上使用注解

    @Service

@Service("userService")
public class UserServiceImpl implements UserService{
}
           
  • UserController

    類上使用注解

    @Controller

@Controller
public class UserController{}
           

配置bean的注解

注解 說明

@Scope

相當于bean标簽的

scope

屬性

@PostConstruct

相當于bean标簽的

init-method

屬性

@PreDestroy

相當于bean标簽的

destory-method

屬性
配置bean的作用範圍:
  • @Scope

    :配置bean的作用範圍,相當于bean标簽的scope屬性。加在bean對象上
  • @Scope

    的常用值有:
  • singleton

    :單例的,容器中隻有一個該bean對象
    • 何時建立:容器初始化時
    • 何時銷毀:容器關閉時
  • prototype

    :多例的,每次擷取該bean時,都會建立一個bean對象
    • 何時建立:擷取bean對象時
    • 何時銷毀:長時間不使用,垃圾回收
@Scope("prototype")
@Service("userService")
public class UserServiceImpl implements UserService{
    //...
}
           
配置bean生命周期的方法
  • @PostConstruct

    是方法級别的注解,用于指定bean的初始化方法
  • @PreDestroy

    是方法級别的注解,用于指定bean的銷毀方法
@Service("userService")
public class UserServiceImpl implements UserService {

    @PostConstruct
    public void init(){
        System.out.println("UserServiceImpl對象已經建立了......");
    }
    
    @PreDestroy
    public void destroy(){
        System.out.println("UserServiceImpl對象将要銷毀了......");
    }
 
    //......
}
           

依賴注入的注解

注解 說明

@Autowired

相當于property标簽的ref

@Qualifier

結合

@Autowired

使用,用于根據名稱注入依賴

@Resource

相當于

@Autowired + @Qualifier

@Value

相當于property标簽的value
注入bean對象
  • @Autowired

    :用于byType注入bean對象,按照依賴的類型,從Spring容器中查找要注入的bean對象
    • 如果找到一個,直接注入
    • 如果找到多個,則以變量名為id,查找bean對象并注入
    • 如果找不到,抛異常
  • @Qualifier

    :是按id注入,但需要和

    @Autowired

    配合使用。
  • @Resource

    :(是jdk提供的)用于注入bean對象(byName注入),相當于

    @Autowired + @Qualifier

絕大多數情況下,隻要使用

@Autowired

注解注入即可

使用注解注入時,不需要set方法了

注入普通值
  • @Value

    :注入簡單類型值,例如:基本資料類型和String
@Service("userService")
public class UserServiceImpl implements UserService{
    
    @Value("zhangsan")//直接注入字元串值
    private String name;
    
    //從properties檔案中找到key的值,注入進來
    //注意:必須在applicationContext.xml中加載了properties檔案才可以使用
    @Value("${properties中的key}")
    private String abc;
    
    //...
}
           

二、注解方式CURD練習

spring-基于注解的IoC

需求描述

  • 使用注解開發帳号資訊的CURD功能

需求分析

  • 使用注解代替某些XML配置,能夠代替的有:
    • dao層bean對象,可以在類上增加注解

      @Repository

    • service層bean對象,可以在類上增加注解

      @Service

    • Service層依賴于dao層,可以使用注解注入依賴

      @AutoWired

  • 不能使用注解代替,仍然要使用XML配置的的有:
    • QueryRunner的bean對象,是DBUtils工具包裡提供的類,我們不能給它的源碼上增加注解
    • 連接配接池的bean對象,是c3p0工具包裡提供的類,我們不能修改源碼增加注解

需求實作

JavaBean

public class Account {
    private Integer id;
    private String name;
    private Double money;

    //get/set...
    //toString...
}
           

dao層代碼

  • AccountDao

    接口
public interface AccountDao {
    Account findById(Integer id) throws SQLException;

    List<Account> queryAll() throws SQLException;

    void save(Account account) throws SQLException;

    void edit(Account account) throws SQLException;

    void delete(Integer id) throws SQLException;
}
           
  • AccountDaoImpl

    實作類
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    
    @Autowired
    private QueryRunner runner;
    
    @Override
    public Account findById(Integer id) throws SQLException {
        return runner.query("select * from account where id = ?", new BeanHandler<>(Account.class), id);
    }

    @Override
    public List<Account> queryAll() throws SQLException {
        return runner.query("select * from account", new BeanListHandler<>(Account.class));
    }

    @Override
    public void save(Account account) throws SQLException {
        runner.update("insert into account(id,name,money) values (?,?,?)", account.getId(), account.getName(), account.getMoney());
    }

    @Override
    public void edit(Account account) throws SQLException {
        runner.update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }

    @Override
    public void delete(Integer id) throws SQLException {
        runner.update("delete from account where id = ?", id);
    }
}
           

service層代碼

  • AccountService

    接口
public interface AccountService {
    Account findById(Integer id) throws SQLException;

    List<Account> queryAll() throws SQLException;

    void save(Account account) throws SQLException;

    void edit(Account account) throws SQLException;

    void delete(Integer id) throws SQLException;
}
           
  • AccountServiceImpl

    接口
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public Account findById(Integer id) throws SQLException {
        return accountDao.findById(id);
    }

    @Override
    public List<Account> queryAll() throws SQLException {
        return accountDao.queryAll();
    }

    @Override
    public void save(Account account) throws SQLException {
        accountDao.save(account);
    }

    @Override
    public void edit(Account account) throws SQLException {
        accountDao.edit(account);
    }

    @Override
    public void delete(Integer id) throws SQLException {
        accountDao.delete(id);
    }
}
           

提供配置

建立

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--開啟注解掃描-->
    <context:component-scan base-package="com.itheima"/>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>
    <!--配置連接配接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///spring"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
    </bean>
</beans>
           

功能測試

public class AccountTest {

    @Test
    public void queryAll() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);
        List<Account> accounts = accountService.queryAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void findById() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);
        Account account = accountService.findById(2);
        System.out.println(account);
    }

    @Test
    public void save() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setName("jerry");
        account.setMoney(2000d);
        accountService.save(account);
    }

    @Test
    public void edit() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setId(2);
        account.setName("tom");
        account.setMoney(10000d);

        accountService.edit(account);
    }

    @Test
    public void delete() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);

        accountService.delete(3);
    }
}
           

三、純注解開發IoC(新注解)

​ 在上邊的CURD練習中,仍然有部配置設定置需要使用

applicationContext.xml

,那麼能不能使用注解替換掉所有的xml呢?Spring提供了一些新注解,可以達到這個目标。

請注意:Spring提供的這部分新注解,并非為了完全代替掉XML,隻是提供了另外一種配置方案

注解簡介

注解 說明

@Configuration

被此注解标記的類,是配置類

@ComponentScan

用在配置類上,開啟注解掃描。使用basePackage屬性指定掃描的包

@PropertySource

用在配置類上,加載properties檔案。使用value屬性指定properties檔案路徑

@Import

用在配置類上,引入子配置類。用value屬性指定子配置類的Class

@Bean

用在配置類的方法上,把傳回值聲明為一個bean。用name/value屬性指定bean的id

注解詳解

1

@Configuration

配置類

簡介
  • @Configuration

    把一個Java類聲明為核心配置類
    • 加上Java類上,這個Java類就成為了Spring的核心配置類,用于代替

      applicationContext.xml

    • @Component

      的衍生注解,是以:核心配置類也是bean,裡邊可以注入依賴
示例
/**
 * 配置類,代替XML配置檔案
 * Configuration注解:把目前類聲明成為一個配置類
 */
@Configuration
public class AppConfig {
}
           

2 配置類上的注解

簡介
  • @ComponentScan

    配置元件注解掃描
    • basePackages

      或者

      value

      屬性:指定掃描的基本包
  • @PropertySource

    用于加載properties檔案
    • value

      屬性:指定propertis檔案的路徑,從類加載路徑裡加載
    • 相當于

      xml

      中的

      <context:property-placeholder location="classpath:jdbc.properteis"/>

  • @Import

    用于導入其它配置類
    • Spring允許提供多個配置類(子產品化配置),在核心配置類裡加載其它配置類
    • 相當于

      xml

      中的

      <import resource="子產品化xml檔案路徑"/>

      标簽
示例
  • 總配置類
@Configuration
@ComponentScan(basePackages="com.itheima")
@PropertySource("classpath:db.properties")
@Import(JdbcConfig.class)
public class AppConfig{
    
}
           
  • 子產品配置類:JdbcConfig。

    被加載的子產品配置類,可以不用再添加

    @Configuration

    注解
public class JdbcConfig{
}
           

3

@Bean

聲明bean

1)

@Bean

定義bean

簡介

  • @Bean

    注解:方法級别的注解
    • 用于把方法傳回值聲明成為一個bean,作用相當于

      <bean>

      标簽
    • 可以用在任意bean對象的方法中,但是通常用在

      @Configuration

      标記的核心配置類中
  • @Bean

    注解的屬性:
    • value

      屬性:bean的id。如果不設定,那麼方法名就是bean的id

示例

@Configuration
public class AppConfig {
    @Bean
    public UserService myService() {
        return new UserServiceImpl();
    }
}
           
2)

@Bean

的依賴注入

簡介

  • @Bean

    注解的方法可以有任意參數,這些參數即是bean所需要的依賴,預設采用byType方式注入
  • 可以在方法參數上增加注解

    @Qualifier

    ,用于byName注入

示例

@Configuration
public class AppConfig {
    @Bean("myService")
    public UserService myService(UserDao userDao) {
        //通過set方法把myDao設定給MyService
        UserService myService = new UserServiceImpl();
        myService.setMyDao(userDao);
        return myServie;
    }
    
    /**
     * 方法參數上使用@Qualifer注解,用于byName注入
     * 		@Qualifer:可以獨立用在方法參數上用于byName注入
     */
    @Bean("myService2")
    public UserService myService2(@Qualifier("myDao2") UserDao userDao){
        return new UserServiceImpl(userDao);
    }
}
           

小結

  • 配置類上要加注解

    @Configuration

  • 要開啟元件掃描,在配置類上

    @ComponentScan("com.itheima")

  • 如果要把jar包裡的類注冊成bean:
    • 在配置類裡加方法,方法上加@Bean,會把方法傳回值注冊bean對象
@Configuration
@ComponentScan("com.itheima")
public class AppConfig{
    
    @Bean(value="person", initMethod="init", destroyMethod="destroy")
    @Scope("prototype")
    public Person p(){
        Person p = new Person();
        p.setName("jack");
        p.setAge(20);
        return p;
    }
    
    //byType注入依賴
    @Bean
    public QueryRunner runner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }
    
    //byName注入依賴:注入名稱為ds的bean對象
    @Bean
    public QueryRunner runner1(@Qualifier("ds") DataSource dataSource){
        return new QueryRunner(dataSource);
    }    
}
           
  • 如果要引入外部的properties檔案,在配置類上加

    @PropertySource("classpath:xxx.properties")

@Configuration
@PropertySource("classpath:jdbc.properties") //引入properties檔案
public class AppConfig{
    
    //把properties裡的資料注入給依賴項(成員變量)
    @Value("${jdbc.driver}")
    private String driver;
    
    @Bean
    public DataSource ds(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        //直接使用成員變量,它的值已經注入了
        ds.setDrvierClass(drvier);
        ....
        return ds;
    }
}
           
  • 引入子產品配置類,在配置類上使用

    @Import(子配置類.class)

@Configuration
@PropertySource("classpath:jdbc.properties") //引入properties檔案
@Import(ServiceConfig.class)
public class AppConfig{

}

//子配置類
public class ServiceConfig{
    //可以引入properties裡的值(隻要這個properties被引入過)
	@Value("${jdbc.driver}")
    private String driver;
}
           

四、純注解方式CURD練習

需求描述

  • 使用Spring的新注解,代替CURD練習裡,

    applicationContext.xml

    的所有配置

需求實作

  • 提供jdbc配置檔案:jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring
jdbc.username=root
jdbc.password=root
           
  • 提供核心配置類:SpringConfig
@Configuration //聲明目前類是一個配置類
@ComponentScan("com.itheima")//開啟注解掃描
@PropertySource("classpath:jdbc.properties")//加載properties資源檔案
public class SpringConfig {

    @Value("${jdbc.driver}")//注入properties中,jdbc.driver的值
    private String driver;

    @Value("${jdbc.url}")//注入properties中,jdbc.url的值
    private String url;

    @Value("${jdbc.username}")//注入properties中,jdbc.username的值
    private String username;

    @Value("${jdbc.password}")//注入properties中,jdbc.password的值
    private String password;

    //聲明一個bean對象:資料庫連接配接池對象,id是dataSource
    @Bean("dataSource")
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    //聲明一個bean對象:QueryRunner對象,id是runner。
    //方法參數DataSource,需要dataSource,使用@Qualifier注入依賴
    @Bean("runner")
    public QueryRunner queryRunner(@Qualifier("dataSource") DataSource dataSource){
        return new QueryRunner(dataSource);
    }
}
           

功能測試

public class AccountTest {

    @Test
    public void queryAll() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);
        List<Account> accounts = accountService.queryAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void findById() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);
        Account account = accountService.findById(2);
        System.out.println(account);
    }

    @Test
    public void save() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setName("jerry");
        account.setMoney(2000d);
        accountService.save(account);
    }

    @Test
    public void edit() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setId(2);
        account.setName("tom");
        account.setMoney(10000d);

        accountService.edit(account);
    }

    @Test
    public void delete() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);

        accountService.delete(3);
    }
}
           

五、注解深入

準備環境

1. 建立Module,引入依賴

<dependencies>
    	<!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    	<!-- Spring整合Junit -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    	<!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    	<!-- snakeyaml,用于解析yaml檔案的工具包 -->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.25</version>
        </dependency>

		<!-- mysql驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    	<!-- c3p0連接配接池 -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
    </dependencies>
           

2. 建立核心配置類

  • com.itheima

    包裡建立核心配置類

    AppConfig

@Configuration
@ComponentScan("com.itheima")
public class AppConfig {
    
}
           

3. 建立單元測試類(基于注解)

  • src\main\test

    com.itheima.test

    包裡建立單元測試類
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class SpringAnnotationTest {

    /**
     * 把IoC容器注入進來,在測試方法裡要使用
     */
    @Autowired
    private ApplicationContext app;

}
           

@ComponentScan

BeanName生成政策

說明
  • 預設的BeanName生成政策:
    • 如果注冊bean時指定了id/name,以配置的id/name作為bean的名稱
    • 如果沒有指定id/name,則以類名首字母小字作為bean的名稱
  • 在子產品化開發中,多個子產品共同組成一個工程。
    • 可能多個子產品中,有同名稱的類,按照預設的BeanName生成政策,會導緻名稱沖突。
    • 這個時候可以自定義beanname生成政策解決問題
  • @ComponentScan

    nameGenerator

    ,可以配置自定義的BeanName生成政策,步驟:
  1. 建立Java類,實作

    BeanNameGenerator

    接口,定義BeanName生成政策
  2. 在注解

    @ComponentScan

    中,使用

    nameGenerator

    指定生成政策即可
示例
  1. com.itheima.beans

    裡建立一個Java類:Course如下
//定義bean的名稱為c
@Component("c")
public class Course {
    private String courseName;
    private String teacher;
	//get/set...
    //toString
}
           
  1. com.itheima.beanname

    裡建立Java類,實作

    BeanNameGenerator

    接口,定義BeanName生成政策
public class MyBeanNameGenerator implements BeanNameGenerator {

    /**
     * 生成bean名稱。當Spring容器掃描到類之後,會調用這個方法,擷取bean的名稱
     *
     * @param definition bean的定義資訊
     * @param registry bean的注冊器,用于管理容器裡的bean,可以:
     *                 注冊新的bean;查詢某個bean;删除bean 等等
     * @return bean的名稱
     */
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        //擷取bean的全限定類名
        String beanClassName = definition.getBeanClassName();
        //擷取bean的類名(去掉包名,隻要類名)
        String shortName = ClassUtils.getShortName(beanClassName);
        //把bean命名為:my+類名
        return "my" + shortName;
    }
}
           
  1. 在注解

    @ComponentScan

    中,使用

    nameGenerator

    指定生成政策
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class
)
public class AppConfig {
}
           
  1. 編寫測試類,測試擷取bean
/**
     *  Bean命名規則
     *      如果定義了bean的名稱,以定義的名稱為準
     *      如果沒有定義bean的名稱,預設情況下:
     *          類名首字母小寫作為bean的名稱(id)。比如:Course,名稱為course
     *          如果類名前2位都是大寫,則仍然使用原類名。比如:URL,名稱仍然是URL
     *
     * 自定義BeanName命名政策:
     *      1. 建立類,實作BeanNameGenerator,自定義名稱生成政策
     *      2. 在核心配置類的@ComponentScan裡,使用nameGenerator指定生成政策名稱
     *
     * 如果使用了自定義的命名政策,則bean的原本的名稱将會失效
     */
    @Test
    public void testBeanName(){
        //擷取Course類型的bean的名稱
        String[] names = app.getBeanNamesForType(Course.class);
        for (String name : names) {
            System.out.println(name);
        }
    }
           

掃描規則過濾器

說明
  • @ComponentScan

    預設的掃描規則:
    • 掃描指定包裡的

      @Component

      及衍生注解(

      @Controller

      ,

      @Service

      ,

      @Repository

      )配置的bean
  • @ComponentScan

    注解也可以自定義掃描規則,來包含或排除指定的bean。步驟:
    1. 建立Java類,實作

      TypeFilter

      接口,重寫

      match

      方法
      • 方法傳回boolean。true表示比對過濾規則;false表示不比對過濾規則
    2. 使用

      @ComponentScan

      注解的屬性,配置過濾規則:
      • includeFilter

        :用于包含指定TypeFilter過濾的類,符合過濾規則的類将被掃描
      • excludeFilter

        :用于排除指定TypeFilter過濾的類,符合過濾規則的類将被排除
示例1-根據注解過濾
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class,
        //使用Component注解标注的類不掃描
        excludeFilters = @ComponentScan.Filter(
            	//過濾類型:根據注解進行過濾
                type = FilterType.ANNOTATION,
            	//要過濾的注解是:Component
                classes = Component.class
        )
)
public class AppConfig {
}
           
示例2-根據指定類過濾
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class,
        //Course及其子類、實作類不掃描
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ASSIGNABLE_TYPE,
                classes = Course.class
        )
)
public class AppConfig {

}
           
示例3-自定義過濾
  1. 建立Java類
//類上有@Component
@Component
public class BeanTest {
	//類裡有單元測試方法
    @Test
    public void test1(){
        System.out.println("---------");
    }
}
           
  1. 編寫過濾器,實作

    TypeFilter

    接口,重寫

    match

    方法
public class TestFilter implements TypeFilter {
    /**
     * 如果被掃描的類是單元測試類(類裡有單元測試方法),則排除掉不要
     *
     * @param metadataReader 被掃描的類的讀取器,可以讀取類的位元組碼資訊、類的注解資訊
     * @param metadataReaderFactory 讀取器的工廠對象
     * @return 是否比對。如果被掃描的類是單元測試類,則比對,
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //擷取被掃描的類上的注解資訊
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //擷取被掃描的類裡,使用@Test注解标記的方法
        Set<MethodMetadata> annotatedMethods = annotationMetadata.getAnnotatedMethods(Test.class.getName());
        //如果有被@Test标記的方法,傳回true;否則,傳回false
        return annotatedMethods!=null && annotatedMethods.size()>0;
    }
}
           
  1. 使用注解

    @ComponentScan

    ,配置過濾規則
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class,
        //單元測試類不掃描(使用自定義的過濾器,排除掉 過濾出來的單元測試類)
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.CUSTOM,
                classes = TestFilter.class
        )
)
public class AppConfig {

}
           
  1. 測試
@Test
    public void testScan(){
        //擷取不到BeanTest,因為這個類裡有單元測試方法,被我們自定義的過濾器給排除掉了
        BeanTest beanTest = app.getBean(BeanTest.class);
        System.out.println(beanTest);
    }
           

@PropertySource

yml

配置檔案介紹

  • 大家以前學習過的常用配置檔案有

    xml

    properties

    兩種格式,但是這兩種都有一些不足:
    • properties

      • 優點:鍵值對的格式,簡單易讀
      • 缺點:不友善表示複雜的層級
    • xml

      • 優點:層次結構清晰
      • 缺點:配置和解析文法複雜
  • springboot采用了一種新的配置檔案:

    yaml

    (或

    yml

    ),它綜合了

    xml

    properties

    的優點。
    • yaml are't markup language

      => yaml
    • 使用空格表示層次關系 :相同空格的配置項屬于同一級
    • 配置格式是

      key:空格value

      ,鍵值對之間用

      :空格

      表示
  • yaml

    檔案示例:
jdbc:
	driver: com.mysql.jdbc.Driver # 注意:英文冒号後邊必須有一個空格
	url: jdbc:mysql:///spring
	username: root
	password: root
	
jedis:
	host: localhost
	port: 6379
           

使用

@PropertySource

加載

yml

說明

  • @PropertySource

    可以使用

    factory

    屬性,配置

    PropertySourceFactory

    ,用于自定義配置檔案的解析
  • 步驟:
    1. 建立

      yaml

      檔案:

      application.yml

    2. 導入依賴

      snakeyaml

      ,它提供了解析yml檔案的功能
    3. 建立Java類,實作

      PropertySourceFactory

      接口,重寫

      createPropertySource

      方法
    4. 使用

      @PropertySource

      注解,配置工廠類

示例

  1. resources

    目錄裡建立

    yaml

    檔案:

    application.yml

jdbc:
	driver: com.mysql.jdbc.Driver # 注意:英文冒号後邊必須有一個空格
	url: jdbc:mysql:///spring
	username: root
	password: root
	
jedis:
	host: localhost
	port: 6379
           
  1. pom.xml

    增加導入依賴

    snakeyaml

    ,它提供了解析yml檔案的功能
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.25</version>
</dependency>
           
  1. 建立Java類,實作

    PropertySourceFactory

    接口,重寫

    createPropertySource

    方法
public class YamlSourceFactory implements PropertySourceFactory {
    /**
     * 解析yaml配置檔案
     * @param name 名稱
     * @param resource 配置檔案EncodedResource對象 
     * @return PropertySource
     */
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        //1. 建立yaml解析的工廠
        YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
        //2. 設定要解析的資源内容
        factoryBean.setResources(resource.getResource());
        //3. 把資源檔案解析成Properties對象
        Properties properties = factoryBean.getObject();
        //4. 把properties封裝成PropertySource對象并傳回
        return new PropertiesPropertySource("application", properties);
    }
}
           
  1. 使用

    @PropertySource

    注解,配置工廠類
@Configuration("appConfig")
@PropertySource(
    value = "classpath:application.yml", 
    factory = YamlSourceFactory.class
)
public class AppConfig {

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

    @Value("${jedis.host}")
    private String jedisHost;

    public void showProps(){
        System.out.println("jdbc.driver: " + this.jdbcDriver);
        System.out.println("jedis.host: " + this.jedisHost);
    }
}
           
  1. 測試
@Test
public void testProps(){
    AppConfig appConfig = app.getBean(AppConfig.class);
    appConfig.showProps();
}
           

@Import

注冊bean的方式

如果要注解方式配置一個bean,可以如下方式:

  • 在類上使用

    @Component

    ,

    @Controller

    ,

    @Service

    ,

    @Repository

    :隻能用于自己編寫的類上,jar包裡的類不能使用(比如ComboPooledDataSource)
  • 在方法上使用

    @Bean

    :把方法傳回值配置注冊成bean到IoC容器,通常用于注冊第三方jar裡的bean
  • 在核心配置類上使用

    @Import

    • @Import(類名.class)

      ,注冊的bean的id是全限定類名
    • @Import(自定義ImportSelector.class)

      :把自定義ImportSelector傳回的類名數組,全部注冊bean
    • @Import(自定義ImportBeanDefinitionRegister.class)

      :在自定義ImportBeanDefinitionRegister裡手動注冊bean

ImportSelector導入器

示例1-直接導入注冊bean
@Configuration
@Import(Catalog.class) //導入Catalog注冊bean
public class AppConfig {
    @Value("${jdbc.driver}")
    private String jdbcDriver;

    @Value("${jedis.host}")
    private String jedisHost;

    public void showProps(){
        System.out.println("jdbc.driver: " + this.jdbcDriver);
        System.out.println("jedis.host: " + this.jedisHost);
    }
}
           
示例2-使用ImportSelector注冊bean
//導入Catalog, 并把MyImportSelector選擇的全限定類名也注冊bean
@Import({Catalog.class, MyImportSelector.class})
@Configuration
public class AppConfig {
    @Value("${jdbc.driver}")
    private String jdbcDriver;

    @Value("${jedis.host}")
    private String jedisHost;

    public void showProps(){
        System.out.println("jdbc.driver: " + this.jdbcDriver);
        System.out.println("jedis.host: " + this.jedisHost);
    }
}

           
示例3-ImportSelector的進階使用

說明

  • springboot架構裡有大量的

    @EnableXXX

    注解,底層就是使用了ImportSelector解決了子產品化開發中,如何啟動某一子產品功能的問題
  • 例如:
    • 我們開發了一個工程,要引入其它的子產品,并啟動這個子產品的功能:把這個子產品的bean進行掃描裝載

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-UHnxxsjF-1607529819414)(img/image-20200713154934953.png)]

  • 步驟:
    1. 建立一個Module:module_a
      1. 在包

        com.a

        裡建立幾個Bean
      2. 建立核心配置檔案AConfig,掃描

        com.a

      3. 定義一個ImportSelector:

        AImportSelector

        ,導入

        AConfig

      4. 定義一個注解

        @EnableModuleA

    2. 在我們的Module的核心配置檔案

      AppConfig

      上增加注解:

      @EnableModuleA

      1. 引入module_a的坐标
      2. 測試能否擷取到Module a裡的bean

第一步:建立新Module:

module_a

<!-- module_a的坐标 -->
<groupId>com.itheima</groupId>
    <artifactId>spring_module_a</artifactId>
    <version>1.0-SNAPSHOT</version>
           
spring-基于注解的IoC
  1. 在包

    com.a.beans

    裡建立類

    DemoBeanA

    @Component
    public class DemoBeanA {
    }
               
  2. 建立核心配置類

    AConfig

@Configuration
@ComponentScan("com.a")
public class AConfig {
}
           
  1. 建立導入器:建立Java類,實作

    ImportSelector

    接口,重寫

    selectImports

    方法
public class AImportSelector implements ImportSelector {
    /**
     * @param importingClassMetadata。被@Import注解标注的類上所有注解的資訊
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{AConfig.class.getName()};
    }
}
           
  1. 定義注解

    @EnableModuleA

@Import(AImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableModuleA {
}
           

第二步:引入module_a,啟動子產品a

  1. 在我們自己的工程pom.xml裡增加依賴
<dependency>
    <groupId>com.itheima</groupId>
    <artifactId>spring_module_a</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
           
  1. 在我們自己的工程核心配置類上,使用注解@EnableModuleA啟動子產品a
@Configuration
@EnableModuleA
public class AppConfig {

}
           
  1. 在我們自己的工程裡測試
@Test
    public void testBean(){
        //在我們自己的工程裡,可以擷取到module_a裡的bean
        DemoBeanA demoBeanA = app.getBean(DemoBeanA.class);
        System.out.println(demoBeanA);
    }
           

ImportBeanDefinitionRegister注冊器

說明
  • ImportBeanDefinitionRegister

    提供了更靈活的注冊bean的方式
  • AOP裡的

    @EnableAspectJAutoProxy

    就使用了這種注冊器,用于注冊不同類型的代理對象
  • 步驟:
    1. 建立注冊器:

      建立Java類,實作

      ImportBeanDefinitionRegister

      接口,重寫

      registerBeanDefinitions

      方法
    2. 在核心配置類上,使用

      @Import

      配置注冊器
示例
  1. 建立類

    com.other.Other

public class Other {
    public void show(){
        System.out.println("Other.show....");
    }
}
           
  1. 建立注冊器
public class CustomImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    /**
     * @param importingClassMetadata 目前類的注解資訊
     * @param registry 用于注冊bean的注冊器
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //擷取bean定義資訊
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition("com.other.Other").getBeanDefinition();
        //注冊bean,方法參數:bean的id,bean的定義資訊
        registry.registerBeanDefinition("other", beanDefinition);
    }
}
           
  1. 在核心配置類上,使用

    @Import

    配置注冊器
@Configuration
@Import({CustomImportBeanDefinitionRegister.class})
public class AppConfig {

}
           
  1. 測試
@Test
public void testImportRegister(){
    //擷取掃描範圍外,使用ImportBeanDefinitionRegister注冊的bean
    Other other = app.getBean("other",Other.class);
    other.showOther();
}
           

@Conditional

說明

  • @Conditional

    加在bean上,用于選擇性的注冊bean:
    • 符合Condition條件的,Spring會生成bean對象 存儲容器中
    • 不符合Condition條件的,不會生成bean對象
  • 示例:
    • 有一個類Person(姓名name,年齡age)
    • 如果目前作業系統是Linux:就建立Person(linus, 62)對象,并注冊bean
    • 如果目前作業系統是Windows:就建立Persion(BillGates, 67)對象,并注冊bean
  • 步驟
    1. 建立Person類
    2. 建立兩個Java類,都實作

      Condition

      接口:
      • WindowsCondition:如果目前作業系統是Windows,就傳回true
      • LinuxCondition:如果目前作業系統是Linux,就傳回true
    3. 在核心配置類裡建立兩個bean
      • 一個bean名稱為bill,加上

        @Conditional(WindowsCondition.class)

      • 一個bean名稱為linus,加上

        @Conditional(LinuxCondition.class)

示例

  1. 建立Person類
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
 	//get/set...
    //toString...
}
           
  1. 建立兩個Java類,實作

    Condition

    接口
public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		//從目前運作環境中擷取作業系統名稱
        String osName = context.getEnvironment().getProperty("os.name");
		//判斷是否是Windows系統;如果是,傳回true;否則傳回false
        return osName.contains("Windows");
    }
}
           
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		//從目前運作環境中擷取作業系統名稱
        String osName = context.getEnvironment().getProperty("os.name");
		//判斷是否是Linux系統;如果是,傳回true;否則傳回false
        return osName.contains("Linux");
    }
}
           
  1. 核心配置類
@Configuration
public class AppConfig {
	/**
	 * 如果作業系統是Windows,則注冊bean對象:id為bill
	 */
    @Bean
    @Conditional(WindowsCondition.class)
    public Person bill(){
        return new Person("Bill Gates", 67);
    }

    /**
     * 如果作業系統是Linux,注冊bean對象:id為linus
     */
    @Bean
    @Conditional(LinuxCondition.class)
    public Person linus(){
        return new Person("Linux", 60);
    }
}
           
  1. 測試
@Test
public void testCondition(){
    ApplicationContext app = new AnnotationConfigApplicationContext(AppConfig.class);
    Person person = app.getBean(Person.class);
    System.out.println(person);
}
           

執行一次單元測試方法之後,按照以下方式,可以通過JVM參數的方式,設定os.name的參數值。

設定之後,再次執行單元測試方法

spring-基于注解的IoC

Conditional的擴充注解

@Conditional

在springboot裡應用非常多,以下列出了一些

@Conditional

的擴充注解:

  • @ConditionalOnBean:當容器中有指定Bean的條件下進行執行個體化。
  • @ConditionalOnMissingBean:當容器裡沒有指定Bean的條件下進行執行個體化。
  • @ConditionalOnClass:當classpath類路徑下有指定類的條件下進行執行個體化。
  • @ConditionalOnMissingClass:當類路徑下沒有指定類的條件下進行執行個體化。
  • @ConditionalOnWebApplication:當項目是一個Web項目時進行執行個體化。
  • @ConditionalOnNotWebApplication:當項目不是一個Web項目時進行執行個體化。
  • @ConditionalOnProperty:當指定的屬性有指定的值時進行執行個體化。
  • @ConditionalOnExpression:基于SpEL表達式的條件判斷。
  • @ConditionalOnJava:當JVM版本為指定的版本範圍時觸發執行個體化。
  • @ConditionalOnResource:當類路徑下有指定的資源時觸發執行個體化。
  • @ConditionalOnJndi:在JNDI存在的條件下觸發執行個體化。
  • @ConditionalOnSingleCandidate:當指定的Bean在容器中隻有一個,或者有多個但是指定了首選的Bean時觸發執行個體化。

@Profile

說明
  • 在開發中,我們編寫的工程通常要部署不同的環境,比如:開發環境、測試環境、生産環境。不同環境的配置資訊是不同的,比如:資料庫配置資訊;如果每次切換環境,都重新修改配置的話,會非常麻煩,且容易出錯
  • 針對這種情況,Spring提供了

    @Profile

    注解:可以根據不同環境配置不同的bean,激活不同的配置
    • @Profile

      注解的底層就是

      @Conditional

  • 例如:
    • 定義三個資料源:
      • 開發環境一個

        DataSource

        ,使用

        @Profile

        配置環境名稱為

        dev

      • 測試環境一個

        DataSource

        ,使用

        @Profile

        配置環境名稱為

        test

      • 生産環境一個

        DataSource

        ,使用

        @Profile

        配置環境名稱

        pro

    • 在測試類上,使用

      @ActiveProfiles

      激活哪個環境,哪個環境的資料源會生效
      • 實際開發中有多種方式可以進行激活,這裡示範一個單元測試類裡是怎樣激活的
示例
  1. pom.xml

    中增加導入依賴

    mysql驅動

    ,

    c3p0

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
           
  1. 在配置類裡建立三個資料源,并配置

    @Profile

@Configuration
public class AppConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() throws PropertyVetoException {
        System.out.println("dev  開發環境的");
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///devdb");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setMaxPoolSize(20);
        return dataSource;
    }

    @Bean
    @Profile("test")
    public DataSource testDataSource() throws PropertyVetoException {
        System.out.println("test 測試環境的");
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///testdb");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setMaxPoolSize(100);
        return dataSource;
    }

    @Bean
    @Profile("pro")
    public DataSource proDataSource() throws PropertyVetoException {
        System.out.println("pro 生産環境的");
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///prodb");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setMaxPoolSize(200);
        return dataSource;
    }
}
           
  1. 在測試類上,使用

    @ActiveProfiles

    激活哪個環境,哪個環境的資料源會生效
    或者使用JVM參數

    -Dspring.profiles.active=dev

@ActiveProfiles("dev") //激活dev環境
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class SpringTest {
    
    @Autowired
    private ApplicationContext app;

    @Test
    public void testProfile(){
        DataSource dataSource = app.getBean(DataSource.class);
        System.out.println(dataSource);
    }
}