IOC控制反转
- spring概念介绍
- IOC (重要,必须掌握)
- spring快速入门
- spring相关API介绍
- Spring配置文件 (重点)
- DBUtil
以上6部分都是基于xml进行开发
- spring注解开发
- spring整合Junit
Spring概述
Spring是分层的 Java SE/EE应用 full-stack(全栈式) 轻量级开源框架
两大核心:以IOC(Inverse Of Control:控制反转)和** AOP**(Aspect Oriented Programming:面向切面编程)
耦合:程序间的依赖关系
解耦:降低程序间的依赖关系(编译器不依赖,运行期才依赖) 思路:去掉程序间的new关键字
解耦思路:配置文件+反射,自定义IOC的时候就是这种思路
优势
方便解耦,简化开发 Spring就是一个容器,可以将所有对象创建和关系维护交给Spring管理 什么是耦合度?对象之间的关系,通常说当一个模块(对象)更改时也需要更改其他模块(对象),这就是 耦合,耦合度过高会使代码的维护成本增加。要尽量解耦
体系结构
ioc容器的概念:spring创建出来的对象都放在ioc容器中
IOC
控制反转(Inverse Of Control)不是什么技术,而是一种设计思想。它的目的是指导我们设计出更
加松耦合的程序
控制:在java中指的是对象的控制权限(创建、销毁)
反转:指的是对象控制权由原来 由开发者在类中手动控制 反转到 由Spring容器控制
实际上就是把对象的创建权给spring
- 传统方式 之前我们需要一个userDao实例,需要开发者自己手动创建 new UserDao();
- IOC方式 现在我们需要一个userDao实例,直接从spring的IOC容器获得,对象的创建权交给了spring控制
自定义IOC容器
因为SpringIOC容器的思想是类似的,所以这里自定义一个看看原理
需求:实现service层与dao层代码解耦合
先用传统方式实现service层与dao层的调用:
- 创建java项目,导入自定义IOC相关坐标
- 编写Dao接口和实现类
- 编写Service接口和实现类
- 编写测试代码
1 创建java项目,导入自定义IOC相关坐标
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
dom4j用于读写XML文件,jaxen是一个Java编写的开源的XPath库,junit是一个Java语言的单元测试框架
2.编写Dao接口和实现类
接口
public interface IUserDao {
public void save();
}
实现类
public class UserDaoImpl implements IUserDao {
@Override
public void save() {
System.out.println("dao被调用了,保存成功");
}
}
3编写service层接口和实现类
接口
public interface IUserService {
public void save();
}
实现类
public class UserServiceImpl implements IUserService {
@Override
public void save() {
//调用dao层方法 传统方式
IUserDao userDao = new UserDaoImpl();
userDao.save();
}
}
4 编写测试代码
@Test
public void test1(){
//获取业务层对象
IUserService userService = new UserServiceImpl();
//调用save方法
userService.save();
}
这是在传统方式下,调用dao成功了
因为是直接new出来,所以会存在编译期依赖,耦合重
当前service对象和dao对象耦合度太高,而且每次new的都是一个新的对象,导致服务器压力过大。解耦合的原则是编译期不依赖,而运行期依赖就行了
** 下面开始去掉编译期依赖**
步骤:
- 准备一个配置文件beans.xml 把要创建对象的类路径封装进去
- 编写一个工厂工具类,工厂类中使用dom4j来解析配置文件,获取到类全路径
- 使用反射生成对应类的实例对象,存到map中(IOC容器)
编写beans.xml
采用反射技术来代替new对象
把所有需要创建对象的信息定义在配置文件中
这个beans.xml就是配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!--id:标识 class:存的是要生成实例的类的全路径-->
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"></bean>
</beans>
编写BeanFactory工具类
public class BeanFactory {
private static Map<String, Object> ioc = new HashMap<>();
//程序启动时,初始化对象实例
static {
//1.读取配置文件
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 2.解析xml(dom4j)
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
//3.编写xpath表达式
String xpath="//bean";
//4.获取所有的bean标签
List<Element> list = document.selectNodes(xpath);
//5.遍历并使用反射创建对象实例,设置到map集合(ioc容器)中
for(Element element:list){
String id = element.attributeValue("id");
String className = element.attributeValue("class");
//使用反射生成实例对象
Object object = Class.forName(className).newInstance();
//存到map中 key-value格式:id-object
ioc.put(id,object);
}
} catch (DocumentException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 获取指定id的对象实例
public static Object getBean(String beandId) {
return ioc.get(beandId);
}
}
List集合中的每个element对应着一个bean标签
修改UserServiceImpl实现类
@Override
public void save() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//调用dao层方法 传统方式
//IUserDao userDao = new UserDaoImpl();
//反射
//IUserDao userDao = (IUserDao) Class.forName("com.lagou.dao.impl.UserDaoImpl").newInstance();
IUserDao userDao = (IUserDao) BeanFactory.getBean("userDao");
userDao.save();
}
就完成了
Spring快速入门
需求:借助spring的IOC实现service层与dao层代码解耦合
步骤:
- 创建java项目,导入spring开发基本坐标
- 编写Dao接口和实现类
- 创建spring核心配置文件
- 在spring配置文件中配置 UserDaoImpl
- 使用spring相关API获得Bean实例
代码实现
1.创建java项目,导入spring开发基本坐标依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.编写Dao接口和实现类
接口
public interface IUserDao {
public void save();
}
实现类
public class UserDaoImpl implements IUserDao {
public void save() {
System.out.println("dao被调用了...");
}
}
3.创建spring核心配置文件
在文件创建在resources里
<?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">
</beans>
4.在spring配置文件中配置 UserDaoImpl
<!--在spring配置文件中配置 UserDaoImpl
id:唯一标识
class:类全路径
-->
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"></bean>
5.使用spring相关API获得Bean实例
@Test
public void testSave() throws Exception {
//获取到了spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象
// 这行代码执行就会加载applicationContext.xml 并会获取到class的值
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//为了拿到实例对象,用key值去取
//这一步是使用上下文对象从IOC容器中获取到了bean对象
IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");
//调用方法
userDao.save();
}
当
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
执行完(加载的同时就创建了bean对象存到容器中)
spring的IOC容器里面就有了key(userDao)-value(com.lagou.dao.impl.UserDaoImpl)这样的实例对象了
applicationContext.getBean("userDao");
调用getBean方法时其实就是从容器中根据userDao这个key取出对应的实例对象
小结:
- 导入坐标
- 创建Bean
- 创建applicationContext.xml
- 在配置文件中进行Bean配置
- 创建ApplicationContext对象,执行getBean
这样就成功完成了dao层和service层的解耦
Spring相关API
BeanFactory
BeanFactory是 IOC 容器的核心接口,它定义了IOC的基本功能
BeanFactory也可以完成配置文件的加载
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
并有如下特点:
在第一次调用getBean()方法时,才创建指定对象的实例
@Test
public void test2() throws Exception {
//核心接口 不会创建bean对象存到容器中
BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
//getBean的时候才真正创建Bean对象存到容器中,当然同时又getBean从容器中取出了这个对象
IUserDao userDao = (IUserDao)xmlBeanFactory.getBean("userDao");
userDao.save();
}
ApplicationContext和BeanFactory的区别:Bean对象的创建时机不同
BeanFactory:当使用getBean方法时才会真正创建Bean对象
ApplicationContext:创建实现类对象的时候就已经创建了Bean对象存到容器中
ApplicationContext
ApplicationContext代表应用上下文对象,可以获得spring中IOC容器的Bean对象
特点:在spring容器启动时,加载并创建所有对象的实例存到容器中了
常用实现类:
1. ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件 推荐使用这种。
2.FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。C盘D盘都无所谓
3. AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
常用方法:
1. Object getBean(String name);
根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。
2. <T> T getBean(Class<T> requiredType);
根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。
3. <T> T getBean(String name,Class<T> requiredType);
根据Bean的id和类型获得Bean实例,解决容器中相同类型Bean有多个情况。
2.根据类型从容器中查找和匹配Bean实例
@Test
public void testSave() throws Exception {
//获取到了spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象
// 这行代码执行就会加载applicationContext.xml 并会获取到class的值
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//为了拿到实例对象,用key值去取
//这一步是使用上下文对象从IOC容器中获取到了bean对象
//1.根据beanid在容器中找对应的bean对象
//IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");
//2.根据类型在容器中进行查询 有可能报错情况:根据当前类型匹配到多个实例
IUserDao userDao = applicationContext.getBean(IUserDao.class);
//调用方法
userDao.save();
}
Spring配置文件
Bean标签的基本配置
如
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"></bean>
详解:
<bean id="" class=""></bean>
* 用于配置对象交由Spring来创建。
* 基本属性:
id:Bean实例在Spring容器中的唯一标识
class:Bean的全限定名 (Spring的底层是用反射生成该类的实例对象存到容器中,Spring要根据全限定名来使用反射进行创建)
* 默认情况下它调用的是类中的 无参构造函数,如果没有无参构造函数则不能创建成功。
一个bean标签代表一个交由Spring创建的对象
Bean标签的作用范围配置
<bean id="" class="" scope=""></bean>
scope属性指对象的作用范围,取值如下:
- singleton 默认值,单例的(只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例)
- prototype 多例的(每次从容器中取出改对象,都会重新创建该对象的实例,类似于new)
- request: WEB项目中,Spring创建一个Bean的对象,也会将对象存入到request域中一份
- session: WEB项目中,Spring创建一个Bean的对象,也会将对象存入到session域中
- global session:WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession 相当
于 session
应用一下<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" scope="singleton"></bean>:
测试,比较创建对象的地址值
/**
*测试scope属性 scope="singleton"的效果
*/
@Test
public void test3() throws Exception {
ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
IUserDao userDao2 = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
System.out.println(userDao);
System.out.println(userDao2);
}
说明是单例的
测试<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" scope="prototype"></bean>
再查看其地址是不一样的
Bean生命周期
1. 当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心配置文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
2. 当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
Bean的生命周期:
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
Bean生命周期配置
<bean id="" class="" scope="" init-method="" destroy-method=""></bean>
* init-method:指定类中的初始化方法名称
* destroy-method:指定类中销毁方法名称
在UserDaoImpl中创建两个方法
public void init(){
System.out.println("初始化方法执行了。。。");
}
public void destory(){
System.out.println("销毁方法执行了。。。。");
}
然后对这两个方法进行配置
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" init-method="init" destroy-method="destory"></bean>
并执行测试方法
@Test
public void test4() throws Exception {
ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
IUserDao userDao2 = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
System.out.println(userDao);
System.out.println(userDao2);
}
因为是@Test,所以没来得及打印destory方法就已经被销毁了,如果在Tomcat中是可以打印出来的
Bean实例化三种方式
无参构造方法实例化(上面用的就是这种)(最常用)
工厂静态方法实例化
工厂普通方法实例化
无参构造方法实例化
它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>
工厂静态方法实例化
依赖的jar包中有个A类,A类中有个静态方法m1,m1方法的返回值是一个B对象。如果我们频繁使用
B对象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1方法,直接从IOC容器获得
依赖注入概念
依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情
况。IOC 解耦只是降低他们的依赖关系,但不会消除
例如业务层依旧会调用持久层中的方法来对数据库进行操作
- 这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通
过框架把持久层对象传入业务层,而不用我们自己去获取
先写一个普通的业务层
用无参构造方法创建Dao
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" ></bean>
如下
public interface IUserService {
public void save();
}
public class UserServiceImpl implements IUserService {
@Override
public void save() {
//调用dao层的save方法
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
userDao.save();
}
}
写一个测试方法当作是web层
@Test
public void test5(){
IUserService userService = new UserServiceImpl();
userService.save();
}
将UserService也交给Spring管理
简单的说,就是通过框架把持久层对象传入业务层,而不用我们自己去获取
减少new的出现
在applicationContext.xml中配置
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" ></bean>
<!--配置UserService-->
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl"></bean>
那么
再写一个测试方法当作是web层
@Test
public void test5(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService)classPathXmlApplicationContext.getBean("userService");
userService.save();
}
但是现在Service层和Dao层还是存在依赖关系,Service层中还是要去获取到dao对象
这里是手动维护了UserService和UserDao的关系
现在依赖注入就是借助Spring框架来维护依赖关系
Bean依赖注入方式
实际上就是对上述代码进行改造
总共三种:
- 构造方法注入
- set方法注入
- P命名空间注入
构造方法注入
首先对applicationContext.xml中的bean标签进行改造(原来是直接调用无参构造)
<constructor-arg
这个标签就是提醒spring要使用有参的方式进构造- index="0"代表UserServiceImpl类中的第一个参数进行赋值
- type=“com.lagou.dao.IUserDao” 代表当前第一个参数的实例类型
- ref=“userDao” 前面已经配置好了userDao的实例存到IOC容器中,ref代表引用IOC中的userDao对象把该对象注入到第一个参数中
<?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配置文件中配置 UserDaoImpl
id:唯一标识
class:类全路径
-->
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" ></bean>
<!--配置UserService-->
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
<constructor-arg index="0" type="com.lagou.dao.IUserDao" ref="userDao"/>
</bean>
</beans>
可以简化为
<!--配置UserService-->
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
<!--<constructor-arg index="0" type="com.lagou.dao.IUserDao" ref="userDao"/>-->
<constructor-arg name="userDao" ref="userDao"/>
</bean>
name="userDao"代表要给UserServiceImpl中userDao这个值注入对象
public class UserServiceImpl implements IUserService {
private IUserDao userDao;//接受注入进来的userDao实例,当前还是空的
public UserServiceImpl(IUserDao userDao) {
this.userDao = userDao;//把接收到的参数值赋值给成员变量
}//对有参构造进行生成
@Override
public void save() {
userDao.save();
}
}
测试一下
@Test
public void test5(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService)classPathXmlApplicationContext.getBean("userService");
userService.save();
}
set方式注入
在UserServiceImpl中创建set方法
public class UserServiceImpl implements IUserService {
private IUserDao userDao;//接受注入进来的userDao实例,当前还是空的
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
在applicationContext.xml中配置Spring容器调用set方法进行注入
<!--采用set方法完成依赖注入-->
<property name="userDao" ref="userDao"></property>
ref配置的是引用对象,把容器中userDao这个对象注入给name="userDao"这个属性
set方法比构造方法更经常使用,这两种方法都要求掌握
Bean依赖注入的数据类型
上面操作,都是注入Bean对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进
行注入。注入数据的三种数据类型
- 普通数据类型
- 引用数据类型
- 集合数据类型
其中引用数据类型,之前的操作都是对UserDao对象的引用进行注入的。下面以set方法注入为例,演示普通数据类型和集合数据类型的注入
public class UserDaoImpl implements IUserDao {
private String username;
private Integer age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(Integer age) {
this.age = age;
}
public void save() {
System.out.println("dao被调用了...");
System.out.println(username);
System.out.println(age);
}
}
在applicationContext中的相应配置:
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" >
<!--ref用于引用数据类型的注入,value用于普通数据类型的注入-->
<property name="username" value="zimu"></property>
<property name="age" value="18"></property>
</bean>
就完成了普通数据类型的注入
注入集合数据类型
public class UserDaoImpl implements UserDao {
private List<Object> list;
public void save() {
System.out.println(list);
System.out.println("保存成功了...");
}
}
这里list集合的类型是Object,所以是可以存引用对象的
这里先创建一个User类
并在applicationContext中进行配置
<!--配置user对象-->
<bean id="user" class="com.lagou.domain.User">
<property name="username" value="刘岩"/>
<property name="age" value="18"/>
</bean>
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" >
<!--集合类型的注入-->
<property name="list">
<list>
<value>aaa</value>
<ref bean="user"></ref>
</list>
</property>
</bean>
配置文件模块化
Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分
配置拆解到其他配置文件中,也就是所谓的配置文件模块化
原则:
- 按层进行拆分
- 按业务模块进行拆分
两种方法
1)并列的多个配置文件
ApplicationContext act = new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","...");
2)主从配置文件
<import resource="applicationContext-xxx.xml"/>
注意:
同一个xml中不能出现相同名称的bean,如果出现会报错
多个xml如果出现相同名称的bean,不会报错,但是后加载的会覆盖前加载的bean