Spring(3)IOC及依赖注入(基于注解的实现)
学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样
的,都是要降低程序间的耦合。只是配置的形式不一样。
关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌
握。
环境配置
bean.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">
<!--把对象创建交给spring管理-->
<context:component-scan base-package="domain"></context:component-scan>
</beans>
对具体类的配置
package domain.service.impl;
import domain.service.IAccountService;
import domain.dao.IAccountDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@Service("accountService")
@Scope
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
@Resource(name = "accountDao1")
private IAccountDao accountDao = null;
public AccountServiceImpl() {
System.out.println("对象创建成功");
// System.out.println("a = "+ a);
}
public void saveAccount() {
accountDao.saveAccount();
}
@PostConstruct
public void init() {
System.out.println("初始化。。");
}
@PreDestroy
public void destory() {
System.out.println("销毁方法。。");
}
}
1、常用注解
<bean id="accountService" class="domain.service.impl.AccountServiceImpl" scope="singleton" init-method="" destroy-method="">
<property name="" value="" ref=""></property>
</bean>
1.1、用于创建对象的
作用等同于xml配置中的bean标签
<bean id="" class=""></bean>
1.1.1、Component
作用:把被注解的类创建对象后放入spring容器中
属性:
value:指定当前的bean的id,如果不写那么他的默认值是被注解的类的类名,且首字母小写
@Service("accountDao1")
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存账户成功!!!11111111111");
}
}
1.1.2、Controller,Service,Repository
以上三个注解的作用和属性与Component完全相同,
它们是spring提供的三层架构的注解,使三层对象更加清晰
@Controller :一般用于表现层的注解。
@Service :一般用于业务层的注解。
@Repository :一般用于持久层的注解。
细节:如果注解中有且只有一个属性 要赋值时是 ,且名称是 value ,value 在赋值是可以不写。
1.2、用于注入数据
作用等同于xml中的bean标签下的properties标签
<bean id="" class="">
<property name="" value=""/ref=""></property>
</bean>
1.2.1、@Autowired
作用:自动按照类型注入,只要有唯一的一个bean对象与要注入的类型匹配,就可以注入成功
如果IOC容器中没有与要注入变量同类型的bean,则报错
如果IOC容器中有多个与之类型匹配,在不使用其他注解的情况下,依据要注入变量的名称和bean对象的id进行比较,如果变量名与id相同就可以注入,如果无匹配id则报错
出现位置:
变量和方法上都可以出现
细节:使用注解注入时,set方法非必须
@Autowired
private IAccountDao accountDao = null;
1.2.2、Qualifier:
作用:
在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和
@Autowire 一起使用;但是给方法参数注入时,可以独立使用。
属性:
value:指定 bean 的 id。
@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao = null;
1.2.3、@Resource
作用:
直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
属性:
name:指定 bean 的 id。
@Resource(name = "accountDao1")
private IAccountDao accountDao = null;
1.2.4、@Value
作用:
注入基本数据类型和 String 类型数据的
属性:
value:用于指定数据的值。它可以使用spring中的spEl(spring中的EL表达式)
spEL写法:${表达式}
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
1.3、用于改变作用范围的
作用等同于xml配置中bean标签的scope属性
@Service("accountDao1")
@Scope("prototype")
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存账户成功!!!11111111111");
}
}
1.3.1、 @Scope
作用:
指定 bean 的作用范围。
属性:
value:指定范围的值。
取值:singleton prototype request session globalsession
1.4、改变作用范围
作用等同于xml配置中的init-method和destory-method属性
@PostConstruct
public void init() {
System.out.println("初始化。。");
}
@PreDestroy
public void destory() {
System.out.println("销毁方法。。");
}
1.4.1、@PostConstruct
作用:
用于指定初始化方法。== init-method
1.4.2、@PreDestroy
作用:
用于指定销毁方法。==destory-method
1.5、关于 Spring 注解和 XML
注解的优势:
配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。适用于自己定义的类
XML 的优势:
修改时,不用改源码。不涉及重新编译和部署。适用于通过jar导入的类
2、spring 的纯注解配置
写到此处,基于注解的 IoC 配置已经完成,但是大家都发现了一个问题:我们依然离不开 spring 的 xml 配
置文件,那么能不能不写这个 bean.xml,所有配置都用注解来实现呢?
2.1、问题
之所以我们现在离不开 xml 配置文件,是因为我们有一句很关键的配置:
<!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
如果他要也能用注解配置,那么我们就离脱离 xml 文件又进了一步。
另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现。
<!-- 配置 dbAssit -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</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:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
2.2、新注解说明
2.2.1、@Configuration
作用:
用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用
AnnotationApplicationContext(有@Configuration 注解的类.class)。
属性:
value:用于指定配置类的字节码
细节:当配置类作为AnnotationConfigApplicationContext对象创建容器的参数该注解可以省略
@Configuration
public class SpringCinfiguration {
}
注意:
我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢?
请看下一个注解。
2.2.2、@ComponentScan
作用:通过注解指定spring容器在创建时要扫描的包
属性:value:和basePackages作用相同,都是指定要创建容器要扫描的包
该注解作用相当于在xml中配置的:
<context:component-scan base-package="wf"></context:component-scan
@Configuration
@ComponentScan(basePackages = {"wf","config"})//wf,config是包名
public class SpringCinfiguration {
}
注意:
我们已经配置好了要扫描的包,但是数据源和 JdbcTemplate 对象如何从配置文件中移除呢?
请看下一个注解。
2.2.3、@Bean
作用:把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:
name:用于指定bean对象的id,当不写时默认是当前方法名
细节:
当我们使用注解配置方法时,如果方法有参数,sprig框架就会去容器中查找看是否由可用bean对象
查找方式和Autowired注解的作用一样
@Bean(name = "runner")
public QueryRunner creatQueryRunner(@Qualifier("ds1") DataSource dataSource){
return new QueryRunner(dataSource);
}
注意:
我们已经把数据源和 DBAssit 从配置文件中移除了,此时可以删除 bean.xml 了。
但是由于没有了配置文件,创建数据源的配置又都写死在类中了。如何把它们配置出来呢?
请看下一个注解。
2.2.4、@PropertySource
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring
jdbc.username=root
jdbc.password=123456
作用:
用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到
properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性:
value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:
@Configuration
@PropertySource("classpath:jdbc.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;
}
注意:
此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢?
请看下一个注解。
2.2.5、@Import
作用:用于导入其他的配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问
题。
属性:
value[]:用于指定其他配置类的字节码。
@Configuration
@ComponentScan(basePackages = {"wf","config"})
@Import({JdbcConfig.class})
public class SpringCinfiguration {
}
注意:
我们已经把要配置的都配置好了,但是新的问题产生了,由于没有配置文件了,如何获取容器呢?
请看下一小节。
2.2.6、通过注解获取容器:
AnnotationConfigApplicationContext ca = new AnnotationConfigApplicationContext(SpringCinfiguration.class);
3、Spring 整合 Junit
3.1、问题
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);
IAccountService as = ac.getBean(“accountService”,IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
3.2、解决思路分析
针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就无须手动创建了,问题也就解决了。
我们都知道,junit 单元测试的原理(在 web 阶段课程中讲过),但显然,junit 是无法实现的,因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。
这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了。
3.3 、 配置步骤
3.3.1、导入相关坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
3.3.2、使用@RunWith 注解替换原有运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {}
3.3.3、使用@ContextConfiguration 指定 spring 配置文件的位置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringCinfiguration.class)
public class AccountServiceTest {}
@ContextConfiguration 注解:
locations 属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
3.3.4、使用@Autowired
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringCinfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
}
完整代码
package test;
import config.SpringCinfiguration;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import wf.domain.Account;
import org.junit.Test;
import wf.service.IAccountService;
import java.util.List;
/**
* 使用junit进行单元测试
* spring整合junit的配置
* 1.导入spring整合junit的jar
* 2.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringCinfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testQuery(){
//1.加载配置文件
AnnotationConfigApplicationContext ca = new AnnotationConfigApplicationContext(SpringCinfiguration.class);
//2.获取服务层对象
QueryRunner as = ca.getBean("runner", QueryRunner.class);
QueryRunner as1 = ca.getBean("runner", QueryRunner.class);
System.out.println(as ==as1);
}
@Test
public void testFindAll(){
/* //1.加载配置文件
//, JdbcConfig.class
AnnotationConfigApplicationContext ca = new AnnotationConfigApplicationContext(SpringCinfiguration.class);
//2.获取服务层对象
IAccountService as = ca.getBean("accountService", IAccountService.class);*/
//3.根据对象执行方法
List<Account> accounts = as.findAllAccount();
for (Account account:accounts){
System.out.println(account);
}
}
@Test
public void testFindById(){
/* //1.加载配置文件
AnnotationConfigApplicationContext ca = new AnnotationConfigApplicationContext(SpringCinfiguration.class);
//2.获取服务层对象
IAccountService as = ca.getBean("accountService", IAccountService.class);*/
//3.根据对象执行方法
Account a = as.findOneById(3);
System.out.println(a);
}
@Test
public void testSave(){
Account account = new Account();
account.setName("gx");
account.setMoney(1234.0f);
/*//1.加载配置文件
AnnotationConfigApplicationContext ca = new AnnotationConfigApplicationContext(SpringCinfiguration.class);
//2.获取服务层对象
IAccountService as = ca.getBean("accountService", IAccountService.class);*/
//3.根据对象执行方法
as.saveAccount(account);
}
@Test
public void testUpdate(){
Account account = new Account();
account.setId(4);
account.setName("wf");
account.setMoney(1234.0f);
/* //1.加载配置文件
AnnotationConfigApplicationContext ca = new AnnotationConfigApplicationContext(SpringCinfiguration.class);
//2.获取服务层对象
IAccountService as = ca.getBean("accountService", IAccountService.class);*/
//3.根据对象执行方法
as.updateAccount(account);
}
@Test
public void testDelete(){
/* //1.加载配置文件
AnnotationConfigApplicationContext ca = new AnnotationConfigApplicationContext(SpringCinfiguration.class);
//2.获取服务层对象
IAccountService as = ca.getBean("accountService", IAccountService.class);*/
//3.根据对象执行方法
as.deleteAccount(8);
}
}
也可以在xml配置文件项目中使用此方法整合junit