1 Spring概述
1.1 关于框架
框架的概念
框架:特指软件框架,它是我们在实际开发中解决项目需求的技术集合。运用框架可以大大简化代码的编写,缩短开发周期。同时,对后续负责项目维护的人员降低技术门槛,对系统升级提供灵活可控的标准。当然,框架提高了我们的开发效率,降低了维护成本的同时,也要求我们需要遵循它的规范,要按照它的要求进行代码的编写。
框架的作用
- 提高开发效率
- 增强代码的可重用性
- 节约维护成本
- 提供编写规范
- 解耦顶层实现原理
学好框架的必要性
首先,Spring(包括:springmvc、springboot、springData、springcloud等)和mybatis是企业开发中最基础的两个框架。
如果从企业的角度分析,在实际开发中通常都会采用成熟的框架作为项目某个技术领域的解决方案,而掌握这些基础框架对开发人员是最基本的技能要求。
如果从自身角度分析,我们可以体会到前面学习mybatis框架所带来的优势对比JDBC而言。
1.2 Spring框架
官网地址:https://spring.io/
1.2.1 Spring简介
Spring是分层的JavaSE/EE应用的轻量级开源框架,以IOC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层(Web层)SpringMVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界中众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
老的框架组合:
Struts2+Spring+Hibernate(SSH)
SpringMVC+Spring+MyBaits(SSM)
新的框架组合:
Spring+SpringBoot+MyBatis(新SSM)
1.2.2 Spring发展历史
- 1997年IBM提出了EJB的思想
- 1998年,SUN指定EJB1.0的开发规范
了解EJB
EJB可以说像是一个Web Service,比如EJB编写好的业务组件必须放置在EJB容器上,然后提供接口给客户端访问;功能不仅限于此,EJB标准中提供了很多规范,而这些规范只有在EJB容器上才能正常运行。例如:JBOSS、weblogic,所以EJB是一个重量级的框架。
Spring容器取代了原有的EJB容器,因此以Spring框架为核心的应用无需EJB容器的支持,可以在Web容器中运行。Spring容器管理的不再是复杂的EJB组件,而是POJO(Plain Old Java Object)Bean。
- EJB目前版本是3.x
《Expert one on one J2EE design and development》
阐述了J2EE开发存在的问题以及EJB开发设计的优点及解决方案
Rod Johnson-《Expert one-on-one J2EE Development without EJB》
阐述了J2EE开发不再使用EJB的解决方案(interface21,spring原型)
- 目前Spring框架最新版本spring5.3.4 通用版GA
1.2.3 Spring的优势
- 方便解耦,简化开发
通过Spring提供的IOC容器,可以将对象的依赖关系交由Spring进行控制,避免硬编码所造成的程序过度耦合。用户也不必再为单例模式、属性文件的解析等这些底层的需求编写代码,可以更加专注于上层的业务。
- AOP编程的支持
通过Spring的AOP功能,方便进行面向切面编程,许多不容易用OOP实现的功能可以通过AOP轻松实现。
- 方便程序的测试
可以使用非容器依赖的编程方式进行几乎所有的测试工作
- 方便集成各种优秀的框架
Spring可以降低各种框架的使用难度,提供了对各种框架的支持
- 降低Java API的使用难度
Spring 对JavaEE API进行了封装,例如:JDBC、JavaMail
- Spring源码是经典的学习范例
Spring源码设计精妙、结构清晰、匠心独用等处处体现大师对Java设计模式的灵活运用及Java技术的高深造诣。它的源代码是Java技术最佳实践的范例。
1.2.4 Spring体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dARHEOx-1615378416293)(image\20160910093722279.png)]
1.2.5 Spring Framework的版本
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQQbqJ6C-1615378416294)(image\image-20210309111118348.png)]
2 控制反转 IOC—Inverse Of Control
2.1 程序的耦合
2.1.1 什么是耦合
耦合性(Couping),也叫耦合度,它是对模块之间关联程度的度量
在软件工程中,耦合指的就是对象之间的依赖关系。对象之间的耦合越高,则表明模块的独立性和可重用性越差,且维护成本越高。因此,对象的设计应使类和构件之间的耦合最小。软件设计中通用用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个标准就是高内聚低耦合。
2.1.2 内聚和耦合
- 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应恰好做一件事,它描述的是模块内的功能联系。
- 耦合是软件结构中各个模块之间相互连接的一种度量,耦合强弱取决于模块之间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据的复杂度。程序讲究的低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依赖度却不要那么紧密。
2.1.3 耦合示例
1)初始耦合
前面学习中,接触到最明显的一个因程序耦合而不得不改变的技术点就是JDBC操作的注册驱动
public class JdbcDemo{
public static void main(String[]args){
//1、注册驱动
//第一种注册驱动的方式,缺点就是耦合性强
DriverManager.registerDriver(new com.myql.jdbc.Driver());
//2、第二种注册驱动的方式,缺点就是字符串硬编码,可以使用配置文件解决
Class.forName("com.mysql.jdbc.Driver");
}
}
2)实际开发中耦合具体体现
public interface UserDao{
public void save();
}
public class UserDaoImpl implements UserDao{
public void save(){
}
}
public class UserDaoImpl2 implements UserDao{
}
public class UserService{
/**
在我们使用三层架构中,层和层之间的调用,如果使用具体实现类就会出现内容依赖
在需要更换实现类时,就需要修改源代码,造成当前业务功能的独立性很差,同时也给我们的维护带来了极大的不便
*/
private UserDao userDao=new UserDaoImpl2();
}
总结:
程序的耦合:就是程序之间的依赖关系
包括:
类之间的依赖
方法之间的依赖
解耦:
降低程序之间的依赖关系
实际开发中:
应该做到:编译期不依赖,运行时才依赖
解耦思路:
第一步:使用反射来创建对象,而避免使用new关键字
第二步:通过读取配置文件来获取要创建的对象全限定类名
2.1.4 解决程序的耦合思路
产生类和类之间的耦合的根本原因就是:在一个类中new了另一个类的对象
解决耦合的思路:不在类中创建另外一个类的对象,但是我们还需要另一个类的对象;由别人(Spring)把那个类的对象创建好之后给我用就可以了
2.2 工厂设计模式解耦
2.2.1 解耦及其必要性
解耦,顾名思义,即解除耦合,消除依赖关系。但是在程序开发中,如果两个模块协同工作,则必然存在耦合。如果两个模块之间没有任何的依赖关系,则表示它们是独立的,不会由任何交叉或者协同工作的可能。所以,我们这里所说的解耦并非消除代码之间的耦合,而是降低它们的依赖关系,让依赖关系处在一个合理的范围。
低耦合的程序设计是我们开发中一个最基本的要求,它会使我们开发的功能独立性提高,大大增加了模块的复用性。同时,在后期对项目维护时,降低维护成本、项目后续升级时,减少了重构的风险。它是一个合格程序员所必备的设计理念。
2.2.2 解耦的思路分析
在JDBC中,我们通过反射来注册驱动,不会采用DriverManager.registerDriver(new com.myql.jdbc.Driver());方式来注册驱动,这两种不同的方式,在本质上有明显的区别的:
//1、注册驱动
//第一种注册驱动的方式,缺点就是耦合性强
DriverManager.registerDriver(new com.myql.jdbc.Driver());
//2、第二种注册驱动的方式,缺点就是字符串硬编码,可以使用配置文件解决
Class.forName("com.mysql.jdbc.Driver");
- 第一种方式会导致驱动注册两次,这个问题不影响我们太多,最多就是执行的效率会有所下降。而更重要的问题是,当我们需要更换数据库系统时,而导致我们修改源代码。
- Class.forName的方式则不会出现修改源代码的问题
- 换句话说,在我们使用一些不是频繁创建的对象时,采用反射的方式创建对象显然更加合理。而反射创建时需要提供创建类的全限定类名,这个名称如何写在Java代码中,造成的结构就是仍然修改源代码,所以我们使用配置文件。
2.2.3 设计模式–工厂模式
工厂模式是我们最常用的实例化对象模式了,它是用工厂中的方法替代new创建对象的一种设计模式。
以MyBatis的SqlSession接口为例,它有一个实现类DefaultSqlSession,如果我们要创建该接口的实例对象:
SqlSession sqlSession=new DefaultSqlSession();
可是,实际情况是,通常我们都要在创建SqlSession实例的时候做点初始化的工作,比如解析XML,封装连接数据库的信息等。
在创建对象时,如果有一些不得不做的初始化操作时,我们首先想到的是,可以用构造方法,这样创建对象时就可以写成:
SqlSession sqlSession=new DefaultSqlSession(传入配置文件的路径);
但是,如果创建SqlSession实例时所作的初始化工作不是简单的赋值这样的事情,可能是很长的一段代码,如果也写入构造方法,那你的代码就不是特别的规范。
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build("mybatis-config.xml");
SqlSession sqlSession=factory.openSession();
所以,Mybatis框架在使用时为我们提供了SqlSessionFactory工厂类,通过openSession()方法获取到SqlSession对象,openSession()同时有很多参数,用于实现不同的需求。它支持传入Connection参数来保证连接的一致性,支持传入true或false来保证事务的提交时机不同等等。
2.2.4 运用工厂模式解耦
步骤1:创建业务层接口和实现类
package com.wojia.service;
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 保存账户(此处只是模拟,并不是真正的保存)
*/
public void saveAccount();
}
package com.wojia.service.impl;
import com.wojia.service.IAccountService;
/**
* 账户业务层的实现类
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
@Override
public void saveAccount() {
accountDao= (IAccountDao) BeanFactory.getBean("accountDao");
accountDao.saveAccount();
}
}
步骤2:创建持久层接口和实现类
package com.wojia.dao;
/**
* 账户Dao层接口
*/
public interface IAccountDao {
public void saveAccount();
}
package com.wojia.dao.impl;
import com.wojia.dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("=========账户添加成功============");
}
}
步骤3:配置文件beans.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!--配置service-->
<bean id="accountService" class="com.wojia.service.impl.AccountServiceImpl"></bean>
<!--配置dao-->
<bean id="accountDao" class="com.wojia.dao.impl.AccountDaoImpl"></bean>
</beans>
步骤4:工厂类
package com.wojia.factory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 创建对象
* 步骤:
* 1、解析XML,获取所有对象的全限定类名
* 2、反射创建对象,存储到容器中
* 容器:有查找的需求,使用map集合
* 3、用户需要时,从容器根据id的值获取
*/
public class BeanFactory {
//创建map对象 key:String,value:Object类型,存储通过反射创建好的对象
private static Map<String,Object> map=new HashMap<>();
//静态代码块,加载相关配置信息
static {
//解析XML,获取所有对象的全限定名
SAXReader reader=new SAXReader();
try{
//读取配置文件,获取配置文件的文档对象
Document document=reader.read(BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"));
//获取根节点
Element rootElement=document.getRootElement();
//获取根节点下得所有子节点
List<Element> elementList=rootElement.elements();
//遍历
for(Element element:elementList){
//获取子节点指定的属性值
String idValue=element.attributeValue("id");
String classValue=element.attributeValue("class");
//反射创建对象,存储到容器中
Class clazz=Class.forName(classValue);
//通过Class对象,调用指定字节码的构造方法创建对象
Object obj=clazz.getDeclaredConstructor().newInstance();
//存储到容器中
map.put(idValue,obj);
}
}catch (Exception ce){
ce.printStackTrace();
}
}
public static Object getBean(String id){
return map.get(id);
}
}
步骤5:测试类
package com.wojia.test;
import com.wojia.factory.BeanFactory;
import com.wojia.service.IAccountService;
import org.junit.Test;
public class AccountTest {
@Test
public void test1(){
IAccountService accountService=(IAccountService) BeanFactory.getBean("accountService");
accountService.saveAccount();
IAccountService accountService2=(IAccountService) BeanFactory.getBean("accountService");
System.out.println(accountService2==accountService);
}
}
解析XML,我们使用的是DOM4J解析的方式,需要导入以下依赖
<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.0.0</version>
</dependency>
2.3 关于IOC
2.3.1 分析和IOC的引入
上一节我们通过使用工厂模式,实现了解耦。
它的核心思想:
- 通过Bean工厂读取配置文件使用反射创建对象
- 把创建出来的对象都存储起来,当使用者需要对象的时候,不再自己创建对象,而是调用Bean工厂的方法从容器中获取对象。
这里面需要解释两个问题:
-
第一个:存哪去?
- 分析:由于我们是很多对象,肯定需要一个集合来存储。这时候有Map和List、Set供选择。到底选择Map还是其它集合,就看我们有没有查找的需求,有查找需求,选Map集合。所以我们把对象存储到Map集合,把这个Map集合称之为容器。
-
第二个:什么是工厂?
- 工厂就是负责给我们从容器中获取指定对象的类,这时候我们获取对象的方式发生了改变。原理,我们在获取对象时,都是采用new的方式,时主动的。
-
老方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbbvpIsK-1615378416298)(image\image-20210310091540154.png)]
- 现在我们获取对象时,给工厂要,有工厂为我们查找或者创建对象,是被动的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmRnXfOj-1615378416300)(image\image-20210310092003484.png)]
2.3.2 IOC概述
IOC(Inverse Of Control)中文的解释是”控制反转“,对象的使用者不是创建者,作用是将对象的创建反转给Spring框架来创建和管理。
控制反转怎么去理解呢?其实它反转的是什么呢?反转的是对象的创建工作。举个例子:平常我们在Servlet或者service里面创建对象,都是使用new的方式来直接创建对象,现在有了spring之后,我们就再也不new对象了,而是把对象创建的工作交给spring容器去维护,我们只需要向spring容器要对象即可。
IOC的作用:消减计算机程序的耦合(降低代码中的依赖关系)
2.4 Spring的IOC
从功能的角度来说,Spring是一个非常强大功能全面的框架,在Spring中有两大核心IOC和AOP。
在使用Spring IOC时,它支持纯XML配置或者纯注解配置以及XML和注解混合配置三种方式,我们首先以纯XML配置方式作为入门案例。
2.4.1 基于XML配置的IOC入门示例
需求:解决账户业务层和持久层的依赖关系
步骤1:创建业务层接口和实现类
package com.wojia.service;
/**
* 账户业务层接口
*/
public interface IAccountService {
/**
* 模拟保存账户
*/
public void saveAccount();
}
步骤2:创建持久层接口和实现类
package com.wojia.dao;
/**
* 账户持久层接口
*/
public interface IAccountDao {
/**
* 新增账户,模拟新增操作
*/
public void saveAccount();
}
package com.wojia.dao.impl;
import com.wojia.dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("=========持久层,新增账户,成功=========");
}
}
步骤3:导入Spring 的依赖pom.xml
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
</dependencies>
步骤4:创建Spring的配置文件beans.xml ,文件名任意
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FW1UNROh-1615378416302)(D:\spring\day1\image\image-20210310101140713.png)]
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
步骤5:让spring管理资源,在配置文件中配置业务层和DAO层
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
bean标签:用于配置让spring创建对象,并且存入IOC容器的类
id属性:对象的唯一标识
class属性:指定类的全限定名
-->
<!--配置service-->
<bean id="accountService" class="com.wojia.service.impl.AccountServiceImpl"/>
<!--配置dao-->
<bean id="accountDao" class="com.wojia.dao.impl.AccountDaoImpl"/>
</beans>
步骤6:测试
package com.wojia.test;
import com.wojia.dao.IAccountDao;
import com.wojia.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountTest {
@Test
public void test1(){
//1 使用ApplicationContext接口,就是获取spring IOC容器
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
//2 根据bean标签的id属性获取对象
IAccountService accountService=context.getBean("accountService", IAccountService.class);
System.out.println(accountService);
IAccountDao accountDao=context.getBean("accountDao",IAccountDao.class);
System.out.println(accountDao);
}
}
3 基于XML配置IOC深入
3.1 XML的IOC细节
Spring中Bean工厂类的结构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QvGrs9KC-1615378416306)(image\image-20210310110840725.png)]
3.2 BeanFactory和ApplicationContext的区别
- 通过类结构图可以看出,BeanFactory是Spring 中IOC容器的顶层接口,而ApplicationContext是它的一个子接口,所以ApplicationContext具备BeanFactory提供的全部功能。
- 通常情况下,我们称BeanFactory是Spring IOC的基础容器,而ApplicationContext是容器的高级接口,它比BeanFactory多了很多重要的功能。
- BeanFactory和ApplicationContext的区别:
- 创建对象的时间节点不一样
- ApplicationContext:只要一读取配置文件,默认情况下就会创建对象
- BeanFactory:什么时候使用什么时候创建对象
- 创建对象的时间节点不一样
3.3 ApplicationContext接口的实现类
- ClassPathXmlApplicationContext
- 它是从类的根路径下加载配置文件,推荐使用
- FileSystemXmlApplicationContext
- 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
- AnnotationConfigApplicationContext:
- 当我们使用注解配置容器对象时,需要使用此类来创建Spring容器,它用来读取注解。
3.4 IOC中Bean标签和管理对象的细节
3.4.1 Bean标签对象的获取,getBean()方法
package com.wojia.test;
import com.wojia.dao.IAccountDao;
import com.wojia.service.IAccountService;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountTest {
@Test
public void test1(){
//1 使用ApplicationContext接口,就是获取spring IOC容器
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
//2 根据bean标签的id属性和字节码对象,获取指定类的对象,可以解决一个接口有多个实现类的情况
IAccountService accountService=context.getBean("accountService", IAccountService.class);
System.out.println(accountService);
IAccountDao accountDao=context.getBean("accountDao",IAccountDao.class);
System.out.println(accountDao);
//方式2:根据字节码获取对象
IAccountService accountService1=context.getBean(IAccountService.class);
IAccountDao accountDao1=context.getBean(IAccountDao.class);
System.out.println("根据字节码获取对象"+accountService1);
System.out.println("根据字节码获取对象"+accountDao1);
//方式3:根据bean id属性获取
IAccountService accountService2=(IAccountService) context.getBean("accountService");
IAccountDao accountDao2=(IAccountDao) context.getBean("accountDao");
}
}
3.4.2 Bean标签的作用和属性
- 作用:
- 用于配置对象让Spring来创建
- 默认情况下它调用的是类中无参数的构造方法,如果没有无参数的构造方法则不能创建成功
- 属性:
- id:给对象在容器中提供一个唯一标识,用于获取对象
- class:指定类的全限定名,用于反射创建对象
- scope:指定对象的作用范围
- singleton:默认值,单例模式
- prototype:多例
- request:WEB项目中,Spring创建一个Bean对象,将对象存入到request域中
- session:WEB项目中,Spring创建一个Bean对象,将对象存入到session域中
- init-method:指定类中的初始化方法名称
- destory-method:指定类中销毁方法名称
- 多例对象scope=“prototype”
- 每次访问对象时,都会重新创建对象实例
- 生命周期
- 对象创建:当使用对象时,创建对象的实例
- 对象活着:只要对象在使用中,就一直活着
- 对象死亡:当对象长时间不使用,被Java的垃圾回收器回收
- spring框架只负责创建,不负责销毁
3.4.3 实例化bean的三种方式
方式1:Spring使用默认无参数构造方法
在默认情况下,Spring会根据无参数的构造方法来创建类的对象,如果bean中没有无参数的构造方法,则会失败
方式2:Spring管理静态工厂—使用静态工厂的方法创建对象
此种方式是使用StaticFactory类中的静态方法创建对象,并存入spring容器
步骤1:创建静态工厂类
package com.wojia.factory;
import com.wojia.service.IAccountService;
import com.wojia.service.impl.AccountServiceImpl;
/**
* 静态工厂类
*/
public class StaticFactory {
public static IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
步骤2:编写beans.xml
<!--配置静态工厂类-->
<bean id="accountService2" class="com.wojia.factory.StaticFactory" factory-method="createAccountService"/>
步骤3:测试
@Test
public void test3(){
//1 使用ApplicationContext接口,就是获取spring IOC容器
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
//2 根据bean标签的id属性和字节码对象,获取指定类的对象,可以解决一个接口有多个实现类的情况
IAccountService accountService=context.getBean("accountService2", IAccountService.class);
System.out.println(accountService);
}
方式3:spring管理实例工厂–使用实例工厂的方法创建对象
使用实例工厂创建对象,必须先有工厂类的对象
步骤1:创建实例工厂类
package com.wojia.factory;
import com.wojia.service.IAccountService;
import com.wojia.service.impl.AccountServiceImpl;
/**
* 实例工厂
*/
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
步骤2:编写beans.xml
<!--实例工厂类-->
<bean id="instanceFactory" class="com.wojia.factory.InstanceFactory"></bean>
<!--
业务bean
factory-bean属性:用于指定实例工厂bean的id
factory-method属性:用于指定实例工厂中创建对象的方法名
-->
<bean id="accountService3" factory-bean="instanceFactory" factory-method="createAccountService"/>
步骤3:测试
/**
* 测试实例工厂
*/
@Test
public void test4(){
//1 使用ApplicationContext接口,就是获取spring IOC容器
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
//2 根据bean标签的id属性和字节码对象,获取指定类的对象,可以解决一个接口有多个实现类的情况
IAccountService accountService=context.getBean("accountService3", IAccountService.class);
System.out.println(accountService);
}
4 Spring的依赖注入(DI)
4.1 什么是依赖注入
依赖注入(DI):Dependency Injection。就是让Spring框架给Bean对象的属性进行赋值,它是Spring框架核心IOC的具体体现
- 我们在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况,IOC解耦只是降低它们的依赖关系,但是不会消除。
- 简单的来说,依赖注入(DI)就是坐等框架把所有需要的对象传过来,而不要我们自己去获取。
4.2 DI的实现示例
步骤1:提供一个相应的POJO类
package com.wojia.domain;
import java.io.Serializable;
import java.util.Date;
/**
* JavaBean
*/
public class Account implements Serializable {
private Integer id;
private String accountName;
private double money;
private Date birthday;
public Account(){
}
public Account(Integer id, String accountName, double money, Date birthday) {
this.id = id;
this.accountName = accountName;
this.money = money;
this.birthday = birthday;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", accountName='" + accountName + '\'' +
", money=" + money +
", birthday=" + birthday +
'}';
}
}
4.2.1 构造方法注入
使用构造方法的方式,给POJO中的属性传值
要求:POJO需要提供一个有参数的构造方法
步骤2:编写beans.xml
<!--DI:依赖注入 通过构造方法实现-->
<bean id="account" class="com.wojia.domain.Account">
<!--
constructor-arg标签:通过构造方法注入值
属性:
index:指定参数在构造方法参数列表中的索引位置
ref:引用其它bean对象,作为当前属性的值,指定所引用bean的id属性即可
type:指定参数在构造方法中的数据类型
name:指定参数在构造方法中的名称,用这个name属性值和构造方法中的参数名进行匹配
value:给参数赋值,它能赋值基本数据类型的值也可以是引用类型或者String
-->
<!--通过参数索引赋值-->
<!-- <constructor-arg index="0" type="java.lang.Integer" value="1001"/>-->
<!-- <constructor-arg index="1" type="java.lang.String" value="zhangsan"/>-->
<!-- <constructor-arg index="2" type="double" value="7777.0"/>-->
<!-- <constructor-arg index="3" type="java.util.Date" ref="date"/>-->
<!--通过参数名赋值-->
<!-- <constructor-arg name="id" value="1002"/>-->
<!-- <constructor-arg name="birthday" ref="date"/>-->
<!-- <constructor-arg name="money" value="9999.0"/>-->
<!-- <constructor-arg name="accountName" value="小米"/>-->
<!---->
<constructor-arg value="1001"/>
<constructor-arg value="zhangsan"/>
<constructor-arg value="8888.0"/>
<constructor-arg ref="date"/>
</bean>
<bean id="date" class="java.util.Date"/>
步骤3:测试
/**
* DI 构造方法注入
*/
@Test
public void test5(){
//1 使用ApplicationContext接口,就是获取spring IOC容器
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
Account account=context.getBean("account",Account.class);
System.out.println(account);
}
4.2.2 set方法注入
顾名思义,就是在类中提供需要注入成员的set方法,在实际开发中,使用比较多。
配置beans.xml
<!--DI依赖注入,set方法注入-->
<bean id="account2" class="com.wojia.domain.Account">
<!--
property标签:set方法注入
属性:
name:指定属性名称
value:值
ref:引用某个bean
-->
<property name="id" value="1003"/>
<property name="accountName" value="小明"/>
<property name="money" value="6666.88"/>
<property name="birthday" ref="date"/>
</bean>
测试
/**
* DI set方法注入
*/
@Test
public void test6(){
//1 使用ApplicationContext接口,就是获取spring IOC容器
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
Account account=context.getBean("account2",Account.class);
System.out.println(account);
}
4.2.3 注入集合属性
就是给集合成员变量赋值,它用的是set方式注入,只不过数据类型都是集合。
List、Set、Map、Properties、数组
package com.wojia.domain;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class DataList implements Serializable {
private String[]myStr;
private Set<String> mySet;
private Map<String,String> myMap;
private List<String> myList;
private Properties myProps;
public DataList(){
}
public String[] getMyStr() {
return myStr;
}
public void setMyStr(String[] myStr) {
this.myStr = myStr;
}
public Set<String> getMySet() {
return mySet;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public Map<String, String> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public List<String> getMyList() {
return myList;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public Properties getMyProps() {
return myProps;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
}
配置beans.xml
<!--给集合属性注入值,set方式-->
<bean id="dataList" class="com.wojia.domain.DataList">
<!--数组-->
<property name="myStr">
<array>
<value>小明</value>
<value>小红</value>
<value>小强</value>
</array>
</property>
<!--list集合-->
<property name="myList">
<list>
<value>javase</value>
<value>javaee</value>
<value>javame</value>
</list>
</property>
<!--set集合-->
<property name="mySet">
<set>
<value>spring</value>
<value>mybatis</value>
<value>springmvc</value>
</set>
</property>
<!--map集合-->
<property name="myMap">
<map>
<entry key="a" value="hello"/>
<entry key="b" value="world"/>
<entry key="c" value="china"/>
</map>
</property>
<!--properties-->
<property name="myProps">
<props>
<prop key="bj">北京</prop>
<prop key="sh">上海</prop>
<prop key="jn">济南</prop>
</props>
</property>
</bean>
测试
/**
* DI set方法 集合属性注入
*/
@Test
public void test7(){
//1 使用ApplicationContext接口,就是获取spring IOC容器
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
DataList dataList=context.getBean("dataList",DataList.class);
System.out.println(Arrays.toString(dataList.getMyStr()));
System.out.println(dataList.getMyList());
System.out.println(dataList.getMySet());
System.out.println(dataList.getMyMap());
System.out.println(dataList.getMyProps());
}
- 给List结构的集合属性注入数据时:
- array、list、set这三个标签通用,另外赋值可以之间给value属性即可,也可以使用bean标签配置一个对象,或者用ref标签引用另一个已经配置好的bean的唯一标识
- 在Map结构的集合属性注入数据时:
- map标签使用entry子标签注入值,entry标签可以使用key和value属性存入map中的数据
- 使用value-ref属性指定已经配好的bean的引用
5 Spring整合MyBatis开发
5.1 整合思路
Spring和MyBatis都是框架,整合到底是MyBatis接管Spring,还是Spring接管MyBatis呢?
MyBatis框架是一个持久层ORM框架,而Spring是一个综合性的框架。所以,整合是Spring整合MyBatis,也就是Spring把MyBatis的一些工作接管了,让Spring框架接管SqlSessionFactory工厂的创建,同时再通过读取mapper配置文件创建dao代理实现类,并把它存入IOC容器,包括数据源或者连接池也都交给spring来管理。
Spring和MyBatis它们都有独立的配置文件,我们在整合时,有两种选择。第一种就是保留两个框架的配置文件,第二种就是只保留Spring的配置文件,把mybaits相关的配置都写在spring配置文件中。
5.2 Spring+MyBatis环境搭建
步骤1:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wojia</groupId>
<artifactId>spring_day1_sm</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
<!--spring和mybaits整合的依赖包-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
步骤2:准备相应的配置文件
- MyBatis
- mybatis-config.xml
- sqlMapper.xml
- 日志log4j2.xml
- Spring
- beans.xml
5.3 MyBatis框架搭建
准备数据表
CREATE TABLE student(
stu_id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
stu_name varchar(30),
stu_age int,
stu_score double
)default charset=utf8
INSERT INTO student(stu_name,stu_age,stu_score) values('zhangsan',22,99);
INSERT INTO student(stu_name,stu_age,stu_score) values('lisi',21,49);
INSERT INTO student(stu_name,stu_age,stu_score) values('wangwu',25,69);
INSERT INTO student(stu_name,stu_age,stu_score) values('zhaoliu',29,89);
INSERT INTO student(stu_name,stu_age,stu_score) values('tianqi',23,79);
SELECT * FROM student;
POJO
package com.wojia.pojo;
import java.io.Serializable;
public class Student implements Serializable {
private Integer stuId;
private String stuName;
private Integer stuAge;
private Double stuScore;
public Student(){
}
public Integer getStuId() {
return stuId;
}
public void setStuId(Integer stuId) {
this.stuId = stuId;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public Integer getStuAge() {
return stuAge;
}
public void setStuAge(Integer stuAge) {
this.stuAge = stuAge;
}
public Double getStuScore() {
return stuScore;
}
public void setStuScore(Double stuScore) {
this.stuScore = stuScore;
}
}
持久层接口
package com.wojia.mapper;
import com.wojia.pojo.Student;
import java.util.List;
/**
*
*/
public interface IStudentMapper {
/**
* 添加
* @param student
* @return
*/
public int save(Student student);
/**
* 更新
* @param student
* @return
*/
public int update(Student student);
/**
* 删除
* @param id
* @return
*/
public int delete(Integer id);
/**
* 根据id查询
* @param id
* @return
*/
public Student findById(Integer id);
/**
* 查询所有
* @return
*/
public List<Student> findAll();
}
IStudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wojia.mapper.AreaMapper">
<select id="findById" parameterType="integer" resultType="student">
SELECT <include refid="studentSql"/> FROM student WHERE stu_id=#{id}
</select>
<sql id="studentSql">
stu_id,stu_name,stu_age,stu_score
</sql>
<select id="findAll" resultType="student">
SELECT <include refid="studentSql"/> FROM student
</select>
<insert id="save" parameterType="student">
INSERT INTO student(stu_name,stu_age,stu_score) values(#{stuName},#{stuAge},#{stuScore})
</insert>
<update id="update" parameterType="student">
UPDATE student set stu_name=#{stuName},stu_age=#{stuAge},stu_score=#{stuScore} WHERE stu_id=#{stuId}
</update>
<delete id="delete" parameterType="integer">
DELETE FROM student WHERE stu_id=#{id}
</delete>
</mapper>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
resource 指定属性文件的路径
url:file:///D:\mybatis\workspace\mybatis_day02\src\db.properties
-->
<properties resource="db.properties" />
<!--简化别名配置-->
<typeAliases>
<package name="com.wojia.pojo"/>
</typeAliases>
<!--environments 标签 配置数据源环境
default属性:设置采用的数据源
-->
<environments default="development">
<!--environment 标签配置具体的数据源环境,也可以配置多个
-->
<environment id="development">
<!-- transactionManager标签 配置事务管理器
type=“jdbc"表示mybaits采用jdbc的事务
type="MANAGRED" 表示mybatis不管理事务,交给其它框架管理
-->
<transactionManager type="JDBC"/>
<!--
dataSource标签:数据源信息
type属性用来指定采用的连接池
POOLED 使用数据库连接池
UNPOOLED 不使用数据库连接池
-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--注册SQL映射文件mapper-->
<mappers>
<package name="com.wojia.mapper"/>
</mappers>
</configuration>
db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1/mybatis_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
jdbc.user=root
jdbc.password=root
mybatis框架测试
package com.wojia.test;
import com.wojia.mapper.IStudentMapper;
import com.wojia.pojo.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class StudentTest {
@Test
public void test1() throws IOException {
InputStream inputStream= Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
List<Student> list=sqlSession.getMapper(IStudentMapper.class).findAll();
for(Student student:list){
System.out.println(student);
}
sqlSession.close();
}
}
5.4 Spring框架搭建及整合MyBatis
业务接口及实现类
package com.wojia.service;
import com.wojia.pojo.Student;
import java.util.List;
public interface IStudentService {
/**
* 添加
* @param student
* @return
*/
public int save(Student student);
/**
* 更新
* @param student
* @return
*/
public int update(Student student);
/**
* 删除
* @param id
* @return
*/
public int delete(Integer id);
/**
* 根据id查询
* @param id
* @return
*/
public Student findById(Integer id);
/**
* 查询所有
* @return
*/
public List<Student> findAll();
}
package com.wojia.service.impl;
import com.wojia.mapper.IStudentMapper;
import com.wojia.pojo.Student;
import com.wojia.service.IStudentService;
import java.util.List;
public class StudentServiceImpl implements IStudentService {
private IStudentMapper studentMapper;
@Override
public int save(Student student) {
return studentMapper.save(student);
}
@Override
public int update(Student student) {
return studentMapper.update(student);
}
@Override
public int delete(Integer id) {
return studentMapper.delete(id);
}
@Override
public Student findById(Integer id) {
return studentMapper.findById(id);
}
@Override
public List<Student> findAll() {
return studentMapper.findAll();
}
}
pom.xml文件添加spring整合mybaits的依赖包
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
配置beans.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
">
<!--配置数据源-->
<!--1、加载db.properties,通过文件加载器-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--2、配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置mybatis的SqlSessionFactory工厂-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
<!--批量配置别名-->
<property name="typeAliasesPackage" value="com.wojia.pojo"/>
</bean>
<!--配置动态代理dao-->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--批量扫描-->
<property name="basePackage" value="com.wojia.mapper"/>
</bean>
<!--配置业务层-->
<bean id="studentService" class="com.wojia.service.impl.StudentServiceImpl">
<property name="studentMapper" ref="IStudentMapper"/>
</bean>
</beans>
5.5 整合测试
@Test
public void test2() throws IOException {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
IStudentService studentService=context.getBean("studentService",IStudentService.class);
List<Student> list=studentService.findAll();
for(Student student:list){
System.out.println(student);
}
}