基于注解的IoC
1. 快速入門
需求描述
- 有dao層:
和UserDao
UserDaoImpl
- 有service層:
和UserService
UserServiceImpl
- 使用注解配置bean,并注入依賴
需求分析
- 準備工作:建立Maven項目,導入依賴坐标
-
編寫代碼并注解配置:
編寫dao層、service層代碼,使用注解
配置bean:代替xml裡的@Component
bean
标簽
使用注解
依賴注入:代替xml裡的@Autowired
和property
标簽constructor-arg
- 在配置檔案中開啟元件掃描(掃描注解)
- 測試
需求實作
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();
}
}
步驟小結
- 導入jar包:
spring-context
- 編寫service和dao
-
要注冊bean,就在類上加注解UserServiceImpl
@Component
- 裡邊有一個依賴項要注入:直接在依賴項成員變量上加
@Autowired
- 裡邊有一個依賴項要注入:直接在依賴項成員變量上加
-
要注冊bean,就在類上加注解UserDaoImpl
@Component
-
- 在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的注解
簡介
注解 | 說明 |
---|---|
| 用在類上,相當于bean标簽 |
| 用在web層類上,配置一個bean(是 的衍生注解) |
| 用在service層類上,配置一個bean(是 的衍生注解) |
| 用在dao層類上,配置一個bean(是 的衍生注解) |
-
:類級别的一個注解,用于聲明一個bean,使用不多@Component
-
屬性:bean的唯一辨別。如果不配置,預設以首字母小寫的類名為idvalue
-
-
,作用和@Controller, @Service, @Repository
完全一樣,但更加的語義化,使用更多@Component
-
:用于web層的bean@Controller
-
:用于Service層的bean@Service
-
:用于dao層的bean@Repository
-
示例
-
類上使用注解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的注解
注解 | 說明 |
---|---|
| 相當于bean标簽的 屬性 |
| 相當于bean标簽的 屬性 |
| 相當于bean标簽的 屬性 |
配置bean的作用範圍:
-
:配置bean的作用範圍,相當于bean标簽的scope屬性。加在bean對象上@Scope
-
的常用值有:@Scope
-
:單例的,容器中隻有一個該bean對象singleton
- 何時建立:容器初始化時
- 何時銷毀:容器關閉時
-
:多例的,每次擷取該bean時,都會建立一個bean對象prototype
- 何時建立:擷取bean對象時
- 何時銷毀:長時間不使用,垃圾回收
@Scope("prototype")
@Service("userService")
public class UserServiceImpl implements UserService{
//...
}
配置bean生命周期的方法
-
是方法級别的注解,用于指定bean的初始化方法@PostConstruct
-
是方法級别的注解,用于指定bean的銷毀方法@PreDestroy
@Service("userService")
public class UserServiceImpl implements UserService {
@PostConstruct
public void init(){
System.out.println("UserServiceImpl對象已經建立了......");
}
@PreDestroy
public void destroy(){
System.out.println("UserServiceImpl對象将要銷毀了......");
}
//......
}
依賴注入的注解
注解 | 說明 |
---|---|
| 相當于property标簽的ref |
| 結合 使用,用于根據名稱注入依賴 |
| 相當于 |
| 相當于property标簽的value |
注入bean對象
-
:用于byType注入bean對象,按照依賴的類型,從Spring容器中查找要注入的bean對象@Autowired
- 如果找到一個,直接注入
- 如果找到多個,則以變量名為id,查找bean對象并注入
- 如果找不到,抛異常
-
:是按id注入,但需要和@Qualifier
配合使用。@Autowired
-
:(是jdk提供的)用于注入bean對象(byName注入),相當于@Resource
@Autowired + @Qualifier
絕大多數情況下,隻要使用
@Autowired
注解注入即可
使用注解注入時,不需要set方法了
注入普通值
-
:注入簡單類型值,例如:基本資料類型和String@Value
@Service("userService")
public class UserServiceImpl implements UserService{
@Value("zhangsan")//直接注入字元串值
private String name;
//從properties檔案中找到key的值,注入進來
//注意:必須在applicationContext.xml中加載了properties檔案才可以使用
@Value("${properties中的key}")
private String abc;
//...
}
二、注解方式CURD練習

需求描述
- 使用注解開發帳号資訊的CURD功能
需求分析
- 使用注解代替某些XML配置,能夠代替的有:
- dao層bean對象,可以在類上增加注解
@Repository
- service層bean對象,可以在類上增加注解
@Service
- Service層依賴于dao層,可以使用注解注入依賴
@AutoWired
- dao層bean對象,可以在類上增加注解
- 不能使用注解代替,仍然要使用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,隻是提供了另外一種配置方案
注解簡介
注解 | 說明 |
---|---|
| 被此注解标記的類,是配置類 |
| 用在配置類上,開啟注解掃描。使用basePackage屬性指定掃描的包 |
| 用在配置類上,加載properties檔案。使用value屬性指定properties檔案路徑 |
| 用在配置類上,引入子配置類。用value屬性指定子配置類的Class |
| 用在配置類的方法上,把傳回值聲明為一個bean。用name/value屬性指定bean的id |
注解詳解
1 @Configuration
配置類
@Configuration
簡介
-
把一個Java類聲明為核心配置類@Configuration
- 加上Java類上,這個Java類就成為了Spring的核心配置類,用于代替
applicationContext.xml
- 是
的衍生注解,是以:核心配置類也是bean,裡邊可以注入依賴@Component
- 加上Java類上,這個Java類就成為了Spring的核心配置類,用于代替
示例
/**
* 配置類,代替XML配置檔案
* Configuration注解:把目前類聲明成為一個配置類
*/
@Configuration
public class AppConfig {
}
2 配置類上的注解
簡介
-
配置元件注解掃描@ComponentScan
-
或者basePackages
屬性:指定掃描的基本包value
-
-
用于加載properties檔案@PropertySource
-
屬性:指定propertis檔案的路徑,從類加載路徑裡加載value
- 相當于
中的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
@Bean
1) @Bean
定義bean
@Bean
簡介
-
注解:方法級别的注解@Bean
- 用于把方法傳回值聲明成為一個bean,作用相當于
标簽<bean>
- 可以用在任意bean對象的方法中,但是通常用在
标記的核心配置類中@Configuration
- 用于把方法傳回值聲明成為一個bean,作用相當于
-
注解的屬性:@Bean
-
屬性:bean的id。如果不設定,那麼方法名就是bean的idvalue
-
示例
@Configuration
public class AppConfig {
@Bean
public UserService myService() {
return new UserServiceImpl();
}
}
2) @Bean
的依賴注入
@Bean
簡介
-
注解的方法可以有任意參數,這些參數即是bean所需要的依賴,預設采用byType方式注入@Bean
- 可以在方法參數上增加注解
,用于byName注入@Qualifier
示例
@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
,可以配置自定義的BeanName生成政策,步驟:nameGenerator
- 建立Java類,實作
接口,定義BeanName生成政策BeanNameGenerator
- 在注解
中,使用@ComponentScan
指定生成政策即可nameGenerator
示例
- 在
裡建立一個Java類:Course如下com.itheima.beans
//定義bean的名稱為c
@Component("c")
public class Course {
private String courseName;
private String teacher;
//get/set...
//toString
}
- 在
裡建立Java類,實作com.itheima.beanname
接口,定義BeanName生成政策BeanNameGenerator
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;
}
}
- 在注解
中,使用@ComponentScan
指定生成政策nameGenerator
@Configuration
@ComponentScan(
basePackages = "com.itheima",
nameGenerator = MyBeanNameGenerator.class
)
public class AppConfig {
}
- 編寫測試類,測試擷取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
)配置的bean@Repository
- 掃描指定包裡的
-
注解也可以自定義掃描規則,來包含或排除指定的bean。步驟:@ComponentScan
- 建立Java類,實作
接口,重寫TypeFilter
方法match
- 方法傳回boolean。true表示比對過濾規則;false表示不比對過濾規則
- 使用
注解的屬性,配置過濾規則:@ComponentScan
-
:用于包含指定TypeFilter過濾的類,符合過濾規則的類将被掃描includeFilter
-
:用于排除指定TypeFilter過濾的類,符合過濾規則的類将被排除excludeFilter
-
- 建立Java類,實作
示例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-自定義過濾
- 建立Java類
//類上有@Component
@Component
public class BeanTest {
//類裡有單元測試方法
@Test
public void test1(){
System.out.println("---------");
}
}
- 編寫過濾器,實作
接口,重寫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;
}
}
- 使用注解
,配置過濾規則@ComponentScan
@Configuration
@ComponentScan(
basePackages = "com.itheima",
nameGenerator = MyBeanNameGenerator.class,
//單元測試類不掃描(使用自定義的過濾器,排除掉 過濾出來的單元測試類)
excludeFilters = @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = TestFilter.class
)
)
public class AppConfig {
}
- 測試
@Test
public void testScan(){
//擷取不到BeanTest,因為這個類裡有單元測試方法,被我們自定義的過濾器給排除掉了
BeanTest beanTest = app.getBean(BeanTest.class);
System.out.println(beanTest);
}
@PropertySource
yml
配置檔案介紹
yml
- 大家以前學習過的常用配置檔案有
和xml
兩種格式,但是這兩種都有一些不足:properties
-
:properties
- 優點:鍵值對的格式,簡單易讀
- 缺點:不友善表示複雜的層級
-
:xml
- 優點:層次結構清晰
- 缺點:配置和解析文法複雜
-
- springboot采用了一種新的配置檔案:
(或yaml
),它綜合了yml
和xml
的優點。properties
-
=> yamlyaml are't markup language
- 使用空格表示層次關系 :相同空格的配置項屬于同一級
- 配置格式是
,鍵值對之間用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
yml
說明
-
可以使用@PropertySource
屬性,配置factory
,用于自定義配置檔案的解析PropertySourceFactory
- 步驟:
- 建立
檔案:yaml
application.yml
- 導入依賴
,它提供了解析yml檔案的功能snakeyaml
- 建立Java類,實作
接口,重寫PropertySourceFactory
方法createPropertySource
- 使用
注解,配置工廠類@PropertySource
- 建立
示例
- 在
目錄裡建立resources
檔案:yaml
application.yml
jdbc:
driver: com.mysql.jdbc.Driver # 注意:英文冒号後邊必須有一個空格
url: jdbc:mysql:///spring
username: root
password: root
jedis:
host: localhost
port: 6379
- 在
增加導入依賴pom.xml
,它提供了解析yml檔案的功能snakeyaml
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.25</version>
</dependency>
- 建立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);
}
}
- 使用
注解,配置工廠類@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);
}
}
- 測試
@Test
public void testProps(){
AppConfig appConfig = app.getBean(AppConfig.class);
appConfig.showProps();
}
@Import
注冊bean的方式
如果要注解方式配置一個bean,可以如下方式:
- 在類上使用
,@Component
,@Controller
,@Service
:隻能用于自己編寫的類上,jar包裡的類不能使用(比如ComboPooledDataSource)@Repository
- 在方法上使用
:把方法傳回值配置注冊成bean到IoC容器,通常用于注冊第三方jar裡的bean@Bean
- 在核心配置類上使用
:@Import
-
,注冊的bean的id是全限定類名@Import(類名.class)
-
:把自定義ImportSelector傳回的類名數組,全部注冊bean@Import(自定義ImportSelector.class)
-
:在自定義ImportBeanDefinitionRegister裡手動注冊bean@Import(自定義ImportBeanDefinitionRegister.class)
-
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架構裡有大量的
注解,底層就是使用了ImportSelector解決了子產品化開發中,如何啟動某一子產品功能的問題@EnableXXX
- 例如:
- 我們開發了一個工程,要引入其它的子產品,并啟動這個子產品的功能:把這個子產品的bean進行掃描裝載
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-UHnxxsjF-1607529819414)(img/image-20200713154934953.png)]
- 步驟:
- 建立一個Module:module_a
- 在包
裡建立幾個Beancom.a
- 建立核心配置檔案AConfig,掃描
com.a
- 定義一個ImportSelector:
,導入AImportSelector
類AConfig
- 定義一個注解
@EnableModuleA
- 在包
- 在我們的Module的核心配置檔案
上增加注解:AppConfig
@EnableModuleA
- 引入module_a的坐标
- 測試能否擷取到Module a裡的bean
- 建立一個Module:module_a
第一步:建立新Module:
module_a
<!-- module_a的坐标 -->
<groupId>com.itheima</groupId>
<artifactId>spring_module_a</artifactId>
<version>1.0-SNAPSHOT</version>
- 在包
裡建立類com.a.beans
DemoBeanA
@Component public class DemoBeanA { }
- 建立核心配置類
AConfig
@Configuration
@ComponentScan("com.a")
public class AConfig {
}
- 建立導入器:建立Java類,實作
接口,重寫ImportSelector
方法selectImports
public class AImportSelector implements ImportSelector {
/**
* @param importingClassMetadata。被@Import注解标注的類上所有注解的資訊
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AConfig.class.getName()};
}
}
- 定義注解
@EnableModuleA
@Import(AImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableModuleA {
}
第二步:引入module_a,啟動子產品a
- 在我們自己的工程pom.xml裡增加依賴
<dependency>
<groupId>com.itheima</groupId>
<artifactId>spring_module_a</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 在我們自己的工程核心配置類上,使用注解@EnableModuleA啟動子產品a
@Configuration
@EnableModuleA
public class AppConfig {
}
- 在我們自己的工程裡測試
@Test
public void testBean(){
//在我們自己的工程裡,可以擷取到module_a裡的bean
DemoBeanA demoBeanA = app.getBean(DemoBeanA.class);
System.out.println(demoBeanA);
}
ImportBeanDefinitionRegister注冊器
說明
-
提供了更靈活的注冊bean的方式ImportBeanDefinitionRegister
- AOP裡的
就使用了這種注冊器,用于注冊不同類型的代理對象@EnableAspectJAutoProxy
- 步驟:
-
建立注冊器:
建立Java類,實作
接口,重寫ImportBeanDefinitionRegister
方法registerBeanDefinitions
- 在核心配置類上,使用
配置注冊器@Import
-
示例
- 建立類
com.other.Other
public class Other {
public void show(){
System.out.println("Other.show....");
}
}
- 建立注冊器
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);
}
}
- 在核心配置類上,使用
配置注冊器@Import
@Configuration
@Import({CustomImportBeanDefinitionRegister.class})
public class AppConfig {
}
- 測試
@Test
public void testImportRegister(){
//擷取掃描範圍外,使用ImportBeanDefinitionRegister注冊的bean
Other other = app.getBean("other",Other.class);
other.showOther();
}
@Conditional
說明
-
加在bean上,用于選擇性的注冊bean:@Conditional
- 符合Condition條件的,Spring會生成bean對象 存儲容器中
- 不符合Condition條件的,不會生成bean對象
- 示例:
- 有一個類Person(姓名name,年齡age)
- 如果目前作業系統是Linux:就建立Person(linus, 62)對象,并注冊bean
- 如果目前作業系統是Windows:就建立Persion(BillGates, 67)對象,并注冊bean
- 步驟
- 建立Person類
- 建立兩個Java類,都實作
接口:Condition
- WindowsCondition:如果目前作業系統是Windows,就傳回true
- LinuxCondition:如果目前作業系統是Linux,就傳回true
- 在核心配置類裡建立兩個bean
- 一個bean名稱為bill,加上
@Conditional(WindowsCondition.class)
- 一個bean名稱為linus,加上
@Conditional(LinuxCondition.class)
- 一個bean名稱為bill,加上
示例
- 建立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...
}
- 建立兩個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");
}
}
- 核心配置類
@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);
}
}
- 測試
@Test
public void testCondition(){
ApplicationContext app = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = app.getBean(Person.class);
System.out.println(person);
}
執行一次單元測試方法之後,按照以下方式,可以通過JVM參數的方式,設定os.name的參數值。
設定之後,再次執行單元測試方法
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提供了
注解:可以根據不同環境配置不同的bean,激活不同的配置@Profile
-
注解的底層就是@Profile
@Conditional
-
- 例如:
- 定義三個資料源:
- 開發環境一個
,使用DataSource
配置環境名稱為@Profile
dev
- 測試環境一個
,使用DataSource
配置環境名稱為@Profile
test
- 生産環境一個
,使用DataSource
配置環境名稱@Profile
pro
- 開發環境一個
- 在測試類上,使用
激活哪個環境,哪個環境的資料源會生效@ActiveProfiles
- 實際開發中有多種方式可以進行激活,這裡示範一個單元測試類裡是怎樣激活的
- 定義三個資料源:
示例
- 在
中增加導入依賴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>
- 在配置類裡建立三個資料源,并配置
@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;
}
}
- 在測試類上,使用
激活哪個環境,哪個環境的資料源會生效@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);
}
}