基于注解的IOC案例(新注解)
-
- 1.三層架構的搭建
-
- 1.1 有bean.xml檔案時實作查詢所有功能
- 2. 三種配置類寫法(替代bean.xml檔案)
-
- 2.1主配置類(用于寫公共的配置)
- 2.2資料源子配置類
- 3.spring整合junit
-
- 3.1 思考
- 3.2整合
1.三層架構的搭建
1.1 有bean.xml檔案時實作查詢所有功能
bean.xml配置檔案:
<!-- 告知spring在建立容器時要掃描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入資料源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!-- 配置資料源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--連接配接資料庫的必備資訊-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
Dao的實作類:
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Override
public List<Account> findAllAccount() {
try{
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
Service的實作類:
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
測試類:
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testFindAll() {
//1.擷取擷取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到業務層對象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.執行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
2. 三種配置類寫法(替代bean.xml檔案)
第一種: 配置類作為AnnotationConfigApplicationContext對象建立時的參數(可以傳入多個參數,配置類成并列關系,沒有誰大誰小)傳一個參數時即為主配置類
public void testFindAll() {
//1.擷取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
第二種: 将主配置類的位元組碼當參數傳給AnnotationConfigApplicationContext同時@ComponentScan掃描子配置類的包,并且在子配置類上加@Configuration注解
public void testFindAll() {
//1.擷取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//主配置類
@ComponentScan({"com.java","com.config"})
public class SpringConfiguration {
.....
}
//資料源配置類
@Configuration
public class JdbcConfig {
......
}
第三種: 在主配置類上用@Import()注解一個就夠了(不用掃描子配置類的包,子配置類也不用加@Configuration),可以導入多個子配置類的位元組碼
public void testFindAll() {
//1.擷取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
@ComponentScan("com.java")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
2.1主配置類(用于寫公共的配置)
//@Configuration
@ComponentScan("com.java")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
@Configuration:
- 作用:指定目前類是一個配置類
- 細節: 當配置類作為AnnotationConfigApplicationContext對象建立時的參數時,該注解可以不寫
public void testFindAll() {
//1.擷取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
@ComponentScan:
- 作用:用于通過注解指定spring在建立時要掃描的包
-
屬性:
value:它和base-Package的作用是一樣的,都是用于指定建立容器時要掃描的包。
我們使用此注解就等同于在xml中配置了: <context:component-scan base-package="com.java"></context:component-scan>
@Import:
- 作用:用于導入其他的配置類
-
屬性:
value:用于指定其他配置類的位元組碼。
當我們使用Import的注解之後,有Import注解的類就是父配置類,而導入的都是子配置類
2.2資料源子配置類
@PropertySource("classpath:jdbcConfig.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于建立一個QueryRunner對象
* @param dataSource
* @return
*/
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource1") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 建立資料源對象1
* @return
*/
@Bean(name = "dataSource1")
public DataSource createDataSource(){
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
return ds;
}
/**
* 建立資料源對象2
* @return
*/
@Bean(name = "dataSource2")
public DataSource createDataSource1(){
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
return ds;
}
@PropertySource:
- 作用:用于指定properties檔案的位置
-
屬性:
value:指定檔案的名稱和路徑
關鍵字:classpath,表示類路徑下
@Bean:
- 作用:用于把目前方法的傳回值作為bean對象存入spring的ioc容器中
-
屬性:
name:用于指定bean的id,當不寫時,預設值是目前方法的名稱
-
細節:
當我們使用注解配置方法時,如果方法有參數(public QueryRunner createQueryRunner(DataSource dataSource)),spring架構會去容器查找有沒有可用的bean對象
查找的方式和Autowired注解的作用是一樣的(按類查找):
容器中沒有比對的就報錯,有多個相同類型的bean對象就需要用到 @Qualifier() 注解在參數中指定id,如上代碼。
下面兩個最終實作的效果是不一樣的,第一個隻是一個方法,方法的傳回值并沒有注入到spring容器中,而第二個既建立了對象又存入了spring容器中,是以第一個要用到@Bean注解
/**
* 用于建立一個QueryRunner對象
* @param dataSource
* @return
*/
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource1") DataSource dataSource){
return new QueryRunner(dataSource);
}
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入資料源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
@Qualifier() : 獨立使用
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource1") DataSource dataSource){
return new QueryRunner(dataSource);
}
3.spring整合junit
在測試類中測試多個方法時擷取容器和得到業務層對象的代碼都是重複的
public class AccountServiceTest {
@Test
public void testFindAll() {
//1.擷取容易
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.得到業務層對象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.執行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
解決辦法如下,但是對于測試工程師來說是不會關注這部分的
private ApplicationContext ac;
private IAccountService as;
@Before
public void init(){
//1.擷取容易
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.得到業務層對象
IAccountService as = ac.getBean("accountService",IAccountService.class);
}
3.1 思考
1、應用程式的入口
main方法
2、junit單元測試中,沒有main方法也能執行
junit內建了一個main方法
該方法就會判斷目前測試類中哪些方法有 @Test注解
junit就讓有Test注解的方法執行
3、junit不會管我們是否采用spring架構
在執行測試方法時,junit根本不知道我們是不是使用了spring架構
是以也就不會為我們讀取配置檔案/配置類建立spring核心容器
4、由以上三點可知
當測試方法執行時,沒有Ioc容器,就算寫了Autowired注解,也無法實作注入,也就是下面的as是無法被注入的
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testFindAll() {
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
3.2整合
Springn整合junit的配置
1.導入spring整合junit的jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.使用Junit提供的一個注解把Junit原有不能加載容器的main方法替換了,替換成spring提供的(SpringJUnit4ClassRunner就是Runner的實作了建立了容器),
@RunWith:
@RunWith(SpringJUnit4ClassRunner.class)
3.告知spring的運作器,spring的ioc容器建立是基于xml還是注解的,并且說明位置
@ContextConfiguration:
Locations:指定xml檔案的位置,加上classpath關鍵字,表示在類路徑下
classes:指定注解類所在位置
當我們使用spring 5.x 版本的時候,要求junnit必須是 4.12及以上
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testFindAll() {
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}