学习Spring主要需要掌握两种技术或者说思想,一个IoC(Inversion of Control)控制翻转以及AOP(Aspect Oriented Programming)面向切面编程。这些在之后使用Springboot后都会得到大大的简化,但是这种解耦的思想与方法还有中间使用的设计模式是十分重要的,在面试的时候也经常会被问到。
一、耦合性和解耦
耦合:
1、耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。
2、在软件工程中,对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高,维护成本越高,因此对象的设计应使类和构件之间的耦合最小。
3、分类:有软硬件之间的耦合,还有软件各模块之间的耦合。耦合性是程序结构中各个模块之间相互关联的度量。它取决于各个模块之间的接口的复杂程度、调用模块的方式以及哪些信息通过接口。
这样说起来可能很抽象,拿齿轮进行举例,在解耦之前,各个齿轮相互嵌套,咬合得很紧,要求每个齿轮都维持正常才能保证最后系统的正常运行,但是如果有一个齿轮出了问题整个系统就玩完。就像下图所示:
这些齿轮就像是Java中的类或者对象, 对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。以下就是各类之间复杂的依赖关系:
耦合的概念介绍清楚了,至于解耦就不必多说了,当然是降低对象之间的耦合性的策略了。spring采用的策略就是IoC,即控制反转。
二、工厂模式解耦
首先我们用工厂模式进行一个降低耦合的操作,这样会更利于我们对Spring的IOC的理解。我们先创建这样一个工程,其目录结构如下:
原先我们是通过在业务层中新建 Dao对象,然后执行相应的操作,在表现层中新建业务层对象执行响应的操作,这样就会造成耦合,我们所需要的是一个工厂类帮我们创建对象。因为在工厂类中我们是通过反射来创建对象的,所以这个时候需要一个properties文件来记录我们需要创建对象的全限定类名以及唯一标识。如下:
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
接着我们开始编写工厂类:
package com.itheima.factory;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
/**
* 一个创建Bean对象的工厂
*
* Bean:在计算机英语中,有可重用组件的含义。
* JavaBean:用java语言编写的可重用组件。
* javabean > 实体类
*
* 它就是创建我们的service和dao对象的。
*
* 第一个:需要一个配置文件来配置我们的service和dao
* 配置的内容:唯一标识=全限定类名(key=value)
* 第二个:通过读取配置文件中配置的内容,反射创建对象
*
* 我的配置文件可以是xml也可以是properties
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据Bean的名称获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
// System.out.println(beanPath);
bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
但是这样创建出来的对像是多例的,即每次调用都会创建一个新的对象,其缺点就是执行效率不高;单例对像的缺点是容易出现线程问题,即每次都能对类中的成员变量的改变都会累积(换句话说就是不会对成员变量进行初始化),但是我们的service和dao中都一般不会定义成员变量,要保证每次都初始化可以定义一个局部变量,这样每次调用方法都会对局部变量进行初始化。由于我们有newInstance()这个方法,我们如果每次都创建多个对象的话就会造成资源的浪费,于是我们就想将我们的对象创建为单例的,这样就可以提升执行效率。
思路就是只在第一次加载工厂类时就创建对象,然后把创建出的对象给存起来,然后直接调用这个对象就可以了。既然要存起来就需要容器,这里我们用HashMap来作为容器存储创建的对象。
package com.itheima.bean;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 一个创建Bean对象的工厂
*
* Bean:在计算机英语中,有可重用组件的含义。
* JavaBean:用java语言编写的可重用组件。
* javabean > 实体类
*
* 它就是创建我们的service和dao对象的。
*
* 第一个:需要一个配置文件来配置我们的service和dao
* 配置的内容:唯一标识=全限定类名(key=value)
* 第二个:通过读取配置文件中配置的内容,反射创建对象
*
* 我的配置文件可以是xml也可以是properties
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个Key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
三、Spring的IOC
Spring的IOC正是采用了和工厂模式解耦相同的思路,只是对象的创建和容器创建都不需要我们自己来完成,全部将会交给Spring进行管理,我们只需要进行相应的配置即可。
1.bean对象的配置与获取
只需要唯一标识的id以及全限定类名即可,如下:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
配置完bean对象后就可以从容器中获取相应的对象了,如下:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = (IAccountService)ac.getBean("accountService");
或者
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这样就获取了对象。
ApplicationContext的三个常用实现类:
ClassPathXmlApplicationContext:他可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。 FileSystemXmlApplicationContext:呀可以加载磁盘任意路径下的配置文件(必须有访问的权限) AnnotationConfigApplicationContext:他是用于读取注解创建容器的
核心容器的两个接口引发出的问题:
ApplicationContext:单例对象适用,实际开发一般采用此接口 其在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要已读取完配置文件马上就创建配置文件中配置的对象 ;
BeanFactory:多例对象使用,是一个顶层接口,功能不是很完善 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才真正创建对象
2.bean对象的创建方式、作用范围、生命周期
创建方式:
这个bean对象的创建方式有三种,上面配置的是其中一种,即采用默认构造方法来进行对象的创建,不再多赘述。
第二种创建方式,以普通工厂的方式进行对象的创建:
假设有这样一个工厂类
package com.itheima.factory;
import com.itheima.service.IAccountService;
import com.itheima.service.impl.AccountServiceImpl;
/**
* 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
我们就需要进行如下的配置:
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
第三种创建方式,以静态工厂的形式进行创建
假设有这样一个工厂类,和上面类似,只是其中的方法为静态方法,那么我们需要进行如下配置,比上面的配置会更简单,因为静态类直接通过类名.就可以进行方法调用。
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
作用范围:
主要说的就是scope属性,该属性的取值如下,要弄清楚单例singleton和多例prototype
singleton:单例的(默认值) prototype:多例的 request:作用于web应用的请求范围 session:作用于web应用的会话范围 global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
生命周期:
bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
3.依赖注入
IOC只是降低程序间的耦合而不是消除,所以程序间必然会存在着依赖关系,这个时候我们将这种依赖关系都交给Spring来进行处理,就称为依赖注入。
首先说说能进行注入的数据,主要有三类:
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型
注入的方式主要也有两种:
构造函数注入
set方法注入
先拿基本类型说清楚注入方式,如下是构造方法注入,看这个AccountServiceImpl类:
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
import java.util.Date;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name,Integer age, Date birthday){
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
}
}
使用的标签:constructor-arg 标签出现的位置:bean标签的内部 标签中的属性 type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型 index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始 name:用于指定给构造函数中指定名称的参数赋值,必须是get函数去掉get之后把首字母变小写 常用的 =============以上三个用于指定给构造函数中哪个参数赋值=============================== value:用于提供基本类型和String类型的数据 ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象 优势: 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。 弊端: 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
eg:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
若采用set方法注入,那么AccountServiceImpl类是这样的:
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
import java.util.Date;
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
}
}
和上面的相比标签换了一下,换成了property,然后优缺点相当于交换了
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="name" value="TEST" ></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
由于自己定义的bean类型注入在上面的birthday中已经演示过了,最后只剩下复杂类型注入,依然采用构造方法的形式,类就不列出来了:
复杂类型的注入/集合类型的注入 用于给List结构集合注入的标签: list array set 用于个Map结构集合注入的标签: map props 结构相同,标签可以互换
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myMap">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>
四、基于注解的IOC
基于注解的IOC会让整个开发过程更方便,提升开发效率。首先需要在Spring手册中去查注解相关的属性,并告知其在创建容器时需要扫描的包:
<?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在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为
context名称空间和约束中-->
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>
我们先来看一下这个曾经的xml的配置:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="" init-method="" destroy-method="">
<property name="" value="" | ref=""></property>
</bean>
我们需要做到的是利用注解来将这些xml中提到的属性或标签全部给表示出来,所以注解一共有四类:
用于创建对象的
他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的 Component: 作用:用于把当前类对象存入spring容器中 属性: value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。 Controller:一般用在表现层 Service:一般用在业务层 Repository:一般用在持久层 以上三个注解他们的作用和属性与Component是一模一样。 他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
用于数据注入的
他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的 Autowired: 作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。 如果Ioc容器中有多个类型匹配时: 出现位置: 可以是变量上,也可以是方法上 细节: 在使用注解注入时,set方法就不是必须的了。 Qualifier: 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲) 属性: value:用于指定注入bean的id。 Resource 作用:直接按照bean的id注入。它可以独立使用 属性: name:用于指定bean的id。 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。 另外,集合类型的注入只能通过XML来实现。 Value 作用:用于注入基本类型和String类型的数据 属性: value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式) SpEL的写法:${表达式}
用于改变作用范围的
他们的作用就和在bean标签中使用scope属性实现的功能是一样的 Scope 作用:用于指定bean的作用范围 属性: value:指定范围的取值。常用取值:singleton prototype
用于生命周期的
他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的 PreDestroy 作用:用于指定销毁方法 PostConstruct 作用:用于指定初始化方法
具体使用如下:
@Service("accountService")
//@Scope("prototype")
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
@Resource(name = "accountDao2")
private IAccountDao accountDao = null;
@PostConstruct
public void init(){
System.out.println("初始化方法执行了");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法执行了");
}
public void saveAccount(){
accountDao.saveAccount();
}
}
五、基于XML的IOC的例子
说了这么多,做一个完整的例子来了解SpringIOC的一个完整过程还是十分必要的。
我们做一个简单的账户管理的功能,首先还是创建出该有的类:Dao、Service、Bean类。这里我们就不多说了。主要就是xml的配置,因为我们用到了数据库,所以用了C3P0连接池技术。创建完响应的类以及set方法后,我们开始进行注入。
这里唯一需要注意的是数据源的bean对象和runner的bean对象,前者注入的是String类型,后者是因为怕线程之间冲突,所以配置为多例的,最后结果如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把对象的创建交给spring来管理-->
<!-- 配置Dao对象 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao对象 -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
</bean>
<!-- 配置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/spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
这样就完全配置完成了,接着我们需要将xml转成注解配置的方法,依然是改beans中的属性,然后注明需要扫描的包,然后我们将自己创建的类进行注解标注。这里仅展示AccountServiceImpl和AccountDaoImpl中的配置情况:
AccountServiceImpl:
/**
* 账户的业务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao ;
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
}
AccountDaoImpl
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public List<Account> findAllAccount() {
try {
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return runner.query("select * from account where id=?",new BeanHandler<Account>(Account.class),accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update("insert into account (name,money) values (?,?)",account.getName(),account.getMoney());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try {
runner.update("delete from account where id=?",accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
最后由于数据源和runner我们没法在源码中去加上注释,所以还是存在着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">
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--把对象的创建交给spring来管理-->
<!-- 配置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/spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
这样注解就显得比较鸡肋,所以我们想着能不能将所有的配置都用java或者注解来实现,而不用xml配置文件。
既然没有了xml,但依然需要配置信息,那么我们就需要一个配置类SpringConfiguration,这就要介绍两个新注解ComponentScan和Configyration,这两个注解就是专门用于配置文件的。一个定义了要扫描的包,一个定义了这个类是配置类。
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean(name="dataSource")
public DataSource createDataSource(){
try{
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring_day02");
ds.setUser("root");
ds.setPassword("root");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
写成这样就可以直接去测试了,这里需要注意的是创建容器的时候我们需要采用注解的方式来进行容器的创建:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
这样就算编写成功了,但是还存在以下几个问题:
1.configuration的问题
我们将配置类中的@Configuration注释掉,发现既然还能用,原来,只要在创建容器的时候将字节码即SpringConfiguration.class当参数传进去了就可以实现配置类的加载。这个时候我们观察我们这个配置类,发现我们这个配置类包含的都是一些数据库相关的配置,我们希望将这一部分给抽取出来,专门作为一个数据库配置类,而原先的SpringConfiguration作为一个公共配置类,于是我们就有了:
//SpringConfiguration.class:
@ComponentScan("com.itheima")
public class SpringConfiguration {
}
//JdbaConfig.class:
/*和Spring连接数据库相关的配置类*/
public class JdbcConfig {
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean(name="dataSource")
public DataSource createDataSource(){
try{
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring_day02");
ds.setUser("root");
ds.setPassword("root");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
测试代码中依然是:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
这样运行结果是错误的,因为我们的配置文件在和com包并列的config包中,所以我们需要在@ComponentScan中添加这个包,但是运行依然是错误的,因为我们包在扫描的过程中只有在确定这是一个配置类的时候,才会对配置类中的注解进行解析,所以我们在JdbcConfig类前加上@Configuration,最后我们执行测试方法才是对的。
换句话说,就是@Configuration并不是什么地方都可以不写的,只有当你在创建容器将字节码当参数传递的时候,才可以不写,换句话说你如果JdbcConfig上面不写@Configuration,那么可以将测试代码改成:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class,JdbcConfig.class);
那么有没有更简单的一个操作呢,既不用多传参,也不用多加注解,当然有,那就是采用@import注解,以后@Configuration都不用了,直接传一个参,然后导入另一个配置类:
@Import(JdbcConfig.class)
2.数据库相关配置给写死了
在我们的配置类中有关数据库的相关配置限定死了,我们需要写在一个配置文件中,方便随时更改。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_day02
jdbc.username=root
jdbc.password=root
JdbcConfig中我们采用@value来进行注解
/*和Spring连接数据库相关的配置类*/
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;
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean(name="dataSource")
public DataSource createDataSource(){
try{
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
这样就可以了,需要注意的是在SpringConfiguration中需要加上
@PropertySource("classpath:jdbcConfig.properties")
3.整合Junit
1、应用程序的入口
main方法
2、junit单元测试中,没有main方法也能执行
junit集成了一个main方法
该方法就会判断当前测试类中哪些方法有 @Test注解
junit就让有Test注解的方法执行
3、junit不会管我们是否采用spring框架
在执行测试方法时,junit根本不知道我们是不是使用了spring框架
所以也就不会为我们读取配置文件/配置类创建spring核心容器
4、由以上三点可知
当测试方法执行时,没有Ioc容器,就算写了Autowired注解,也无法实现注入
使用Junit单元测试:测试我们的配置 * Spring整合junit的配置 * 1、导入spring整合junit的jar(坐标) * 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 * @Runwith * 3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置 * @ContextConfiguration * locations:指定xml文件的位置,加上classpath关键字,表示在类路径下 * classes:指定注解类所在地位置 * * 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
最后测试程序编写如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
}