Spring
一、spring概述及IOC理论推导
1.1、spring概述
1.1.1、简介
- Spring:春天—>给软件行业带来了春天!
- 2002,首次推出了Spring框架的雏形:interface21框架!
- Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日发布了1.0正式版。
- Rod Johnson,Spring Framework创始人,著名作者。很难想象Rod Johnson的学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
- Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
- SSH:Struct2 + Spring + Hibernate!
- SSM:SpringMVC + Spring + Mybatis!
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
1.1.2、优点
- Spring是一个开源免费的框架 , 容器 。
- Spring是一个轻量级的框架 , 非侵入式的。
- 控制反转 IoC , 面向切面 Aop。
- 对事物的支持 , 对框架的支持。
一句话概括:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
1.1.3、组成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jT9LZ0IW-1628569586422)(E:\CYR File\Typora\image\image-20210721142520750.png)]
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qbJOzRDE-1628569586426)(E:\CYR File\Typora\image\image-20210721142550924.png)]
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.1.4、拓展
pring Boot与Spring Cloud:
- Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
- Spring Cloud是基于Spring Boot实现的;
- Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
- Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
- SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aFI9q8Ih-1628569586429)(E:\CYR File\Typora\image\image-20210721142827748.png)]
1.2、IOC理论推导
1.2.1、IoC基础
新建一个空白的maven项目
我们先用我们原来的方式写一段代码:
1、先写一个UserDao接口:
public interface UserDao {
public void getUser();
}
2、再去写Dao的实现类:
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
3、然后去写UserService的接口
public interface UserService {
public void getUser();
}
4、最后写Service的实现类:
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
5、测试:
@Test
public void test(){
UserService service = new UserServiceImpl();
service.getUser();
}
这是我们原来的方式 ,那我们现在修改一下 :
把Userdao的实现类增加几个:
public class UserDaoMySqlImpl implements UserDao {
public void getUser() {
System.out.println("MySql获取用户数据!");
}
}
public class UserDaoOracleImpl implements UserDao {
public void getUser() {
System.out.println("Oracle获取用户数据!");
}
}
public class UserDaoSqlServerImpl implements UserDao { public void getUser() { System.out.println("SqlServer获取用户数据!"); }}
紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现类
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoMySqlImpl(); @Override public void getUser() { userDao.getUser(); }}
那么我们要使用Oracle和SQLServer , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 。在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSOb6mIe-1628569586431)(E:\CYR File\Typora\image\image-20210721145537405.png)]
那我们如何去解决呢 ?
我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set去设置 , 我们去代码里修改下 :
public class UserServiceImpl implements UserService { // private UserDao userDao = new UserDaoImpl(); private UserDao userDao; // 利用set进行动态实现值的注入 public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void getUser() { userDao.getUser(); }}
现在去我们的测试类里 , 进行测试 :
public class MyTest { public static void main(String[] args) { // 用户实际调用的是业务层,dao层他们不需要接触 UserServiceImpl userService = new UserServiceImpl(); // userService.getUser(); userService.setUserDao(new UserDaoImpl()); userService.getUser(); userService.setUserDao(new UserDaoOracleImpl()); userService.getUser(); userService.setUserDao(new UserDaoMySqlImpl()); userService.getUser(); userService.setUserDao(new UserDaoSqlServerImpl()); userService.getUser(); }}
大家发现了区别没有 ? 可能很多人说没啥区别 . 但是同学们 , 他们已经发生了根本性的变化 , 很多地方都不一样了 . 仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了,它只负责提供一个接口 。
- 之前,程序是主动创建对象!控制权在程序猿手上!
- 使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象!
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZHYiCpsG-1628569586432)(E:\CYR File\Typora\image\image-20210721145621291.png)]
1.2.2、IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4hihazW-1628569586433)(E:\CYR File\Typora\image\image-20210721145726069.png)]
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twcBOdGL-1628569586434)(E:\CYR File\Typora\image\image-20210721145815445.png)]
二、快速上手Spring
1.1、Hello Spring
导入Jar包
注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项 .
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version></dependency>
编写代码
1、编写一个Hello实体类
package com.chen.pojo;public class Hello { private String str; public String getName() { return str; } public void setName(String str) { this.str = str; } @Override public String toString() { return "Hello{" + "str='" + str + '\'' + '}'; }}
2、编写我们的spring文件 , 这里我们命名为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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--使用Spring来创建对象,在Spring这些都称为Bean 类型 变量名 = new 类型(); Hello hello = new Hello(); id = 变量名 class = new的对象 property 相当于给对象中的属性设置一个值! --> <!--bean就是java对象 , 由Spring创建和管理--> <bean id="hello" class="com.chen.pojo.Hello"> <property name="str" value="Spring"/> </bean></beans>
3、测试
import com.chen.pojo.Hello;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { //获取Spring的上下文对象! ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //我们的对象现在都在Spring中的管理了,我们需要使用,直接去里面取出来就可以! Hello hello = (Hello) context.getBean("hello"); System.out.println(hello.toString()); }}
思考
- Hello 对象是谁创建的 ? hello 对象是由Spring创建的
- Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的
这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的;
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象 ;
依赖注入 : 就是利用set方法来进行注入的
IOC是一种编程思想,由主动的编程变成被动的接收
修改案例一
我们在案例一中, 新增一个Spring配置文件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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDaoImpl" class="com.chen.dao.UserDaoImpl"/> <bean id="UserDaoMySqlImpl" class="com.chen.dao.UserDaoMySqlImpl"/> <bean id="UserDaoOracleImpl" class="com.chen.dao.UserDaoOracleImpl"/> <bean id="UserDaoSqlServerImpl" class="com.chen.dao.UserDaoSqlServerImpl"/> <bean id="UserServiceImpl" class="com.chen.service.UserServiceImpl"> <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写--> <!--引用另外一个bean , 不是用value 而是用 ref--> <property name="userDao" ref="UserDaoMySqlImpl"/> </bean></beans>
测试:
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("UserServiceImpl"); serviceImpl.getUser(); }}
OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象由Spring来创建,管理,装配!
1.2、IOC创建对象方式
通过无参构造方法来创建
1、User.java
package com.chen.pojo;public class User { private String name; public User() { System.out.println("User的无参构造!"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public void show() { System.out.println("name="+name); }}
2、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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--默认通过无参构造方法注入--> <!--<bean id="user" class="com.chen.pojo.User"> <property name="name" value="陈永瑞"/> </bean>--></beans>
3、测试类
import com.chen.pojo.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.show(); }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11LlIp7g-1628569586435)(E:\CYR File\Typora\image\image-20210721152330458.png)]
结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!
通过有参构造方法来创建
1、User.java
package com.chen.pojo;public class User { private String name; /*public User() { System.out.println("User的无参构造!"); }*/ public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void show() { System.out.println("name="+name); }}
2、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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--通过有参构造方法注入:1.下标赋值--> <!--<bean id="user" class="com.chen.pojo.User"> <constructor-arg index="0" value="SSM课程"/> </bean>--> <!--通过有参构造方法注入:2.直接通过参数名赋值--> <bean id="user" class="com.chen.pojo.User"> <constructor-arg name="name" value="SSM课程"/> </bean> <bean id="userT" class="com.chen.pojo.UserT"> </bean> <!--通过有参构造方法注入:3.类型赋值 不建议使用--> <!--<bean id="user" class="com.chen.pojo.User"> <constructor-arg type="java.lang.String" value="SSM课程"/> </bean>--></beans>
3、测试
import com.chen.pojo.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.show(); }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hdh8QkIp-1628569586436)(E:\CYR File\Typora\image\image-20210721151434498.png)]
**结论:**在配置文件加载的时候。其中管理的对象都已经初始化了!
通过setter方法来创建
1、UserServiceImpl.java
package com.chen.service;import com.chen.dao.UserDao;public class UserServiceImpl implements UserService { private UserDao userDao; // 利用set进行动态实现值的注入 public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void getUser() { userDao.getUser(); }}
2、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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDaoImpl" class="com.chen.dao.UserDaoImpl"/> <bean id="UserDaoMySqlImpl" class="com.chen.dao.UserDaoMySqlImpl"/> <bean id="UserDaoOracleImpl" class="com.chen.dao.UserDaoOracleImpl"/> <bean id="UserDaoSqlServerImpl" class="com.chen.dao.UserDaoSqlServerImpl"/> <bean id="UserServiceImpl" class="com.chen.service.UserServiceImpl"> <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写--> <!--引用另外一个bean , 不是用value 而是用 ref--> <property name="userDao" ref="UserDaoMySqlImpl"/> </bean></beans>
3、测试
import com.chen.service.UserServiceImpl;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("UserServiceImpl"); serviceImpl.getUser(); }}
**结论:**只需要在xml配置文件中进行修改就行,所谓的IOC,一句话搞定:对象由Spring来创建,管理,装配!
1.3、Spring配置
别名
<!-- 别名 添加了别名,我们也可以使用别名获取到这个对象 --><alias name="user" alias="userNew"/>
Bean的配置
<!-- id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符 如果配置id,又配置了name,那么name是别名 name可以设置多个别名,可以用逗号,分号,空格隔开 如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象; class是bean的全限定名=包名+类名--><bean id="userT2" name="u1 u2,u3;u4" class="com.chen.pojo.UserT"> <property name="name" value="Spring框架学习"/></bean>
import
团队的合作通过import来实现 .
<?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"> <import resource="beans.xml"/></beans>
测试:
import com.chen.pojo.User;import com.chen.pojo.UserT;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); User userNew = (User) context.getBean("userNew"); UserT u1 = (UserT) context.getBean("u1"); user.show(); userNew.show(); u1.show(); }}
import com.chen.pojo.User;import com.chen.pojo.UserT;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest2 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); User userNew = (User) context.getBean("userNew"); UserT u1 = (UserT) context.getBean("u1"); user.show(); userNew.show(); u1.show(); }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MN9OnPAV-1628569586437)(E:\CYR File\Typora\image\image-20210721154638520.png)]
三、依赖注入(DI)
1.1、概念
- 依赖注入(Dependency Injection,DI).
- 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
- 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
1.2、注入方式
构造器注入
通过无参构造方法
1、User.java
package com.chen.pojo;public class User { private String name; public User() { System.out.println("User的无参构造!"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public void show() { System.out.println("name="+name); }}
2、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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--默认通过无参构造方法注入--> <!--<bean id="user" class="com.chen.pojo.User"> <property name="name" value="陈永瑞"/> </bean>--></beans>
3、测试类
import com.chen.pojo.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.show(); }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v6Gl6tUX-1628569586438)(E:\CYR File\Typora\image\image-20210721152330458.png)]
结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!
通过有参构造方法
1、User.java
package com.chen.pojo;public class User { private String name; /*public User() { System.out.println("User的无参构造!"); }*/ public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void show() { System.out.println("name="+name); }}
2、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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--通过有参构造方法注入:1.下标赋值--> <!--<bean id="user" class="com.chen.pojo.User"> <constructor-arg index="0" value="SSM课程"/> </bean>--> <!--通过有参构造方法注入:2.直接通过参数名赋值--> <bean id="user" class="com.chen.pojo.User"> <constructor-arg name="name" value="SSM课程"/> </bean> <bean id="userT" class="com.chen.pojo.UserT"> </bean> <!--通过有参构造方法注入:3.类型赋值 不建议使用--> <!--<bean id="user" class="com.chen.pojo.User"> <constructor-arg type="java.lang.String" value="SSM课程"/> </bean>--></beans>
3、测试
import com.chen.pojo.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.show(); }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wAz4tP0h-1628569586439)(E:\CYR File\Typora\image\image-20210721151434498.png)]
Set 注入 (重点)
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .
【环境搭建】
-
复杂类型
package com.chen.pojo;public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; }}
-
真实测试对象
package com.chen.pojo;import java.util.*;public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public String[] getBooks() { return books; } public void setBooks(String[] books) { this.books = books; } public List<String> getHobbys() { return hobbys; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public Map<String, String> getCard() { return card; } public void setCard(Map<String, String> card) { this.card = card; } public Set<String> getGames() { return games; } public void setGames(Set<String> games) { this.games = games; } public String getWife() { return wife; } public void setWife(String wife) { this.wife = wife; } public Properties getInfo() { return info; } public void setInfo(Properties info) { this.info = info; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", address=" + address.toString() + ", books=" + Arrays.toString(books) + ", hobbys=" + hobbys + ", card=" + card + ", games=" + games + ", wife='" + wife + '\'' + ", info=" + info + '}'; }}
-
applicationContext.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- setter注入 --> <bean id="address" class="com.chen.pojo.Address"> <property name="address" value="西安"/> </bean> <bean id="student" class="com.chen.pojo.Student"> <!-- 第一种:普通值注入 value --> <property name="name" value="陈永瑞"/> <!-- 第二种:bean注入 ref --> <property name="address" ref="address"/> <!-- 第三种:数组注入 array --> <property name="books"> <array> <value>红楼梦</value> <value>西游记</value> <value>水浒传</value> <value>三国演义</value> </array> </property> <!-- 第四种:list注入 list --> <property name="hobbys"> <list> <value>听歌</value> <value>敲代码</value> <value>看电影</value> </list> </property> <!-- 第五种:map注入 map --> <property name="card"> <map> <entry key="身份证" value="1234567"/> <entry key="手机号" value="1098018"/> </map> </property> <!-- 第六种:set注入 set --> <property name="games"> <set> <value>LOL</value> <value>COC</value> <value>BOB</value> </set> </property> <!-- 第六种:null值注入 null --> <property name="wife"> <null/> </property> <!-- 第七种:properties注入 props --> <property name="info"> <props> <prop key="driver">2020</prop> <prop key="url">男</prop> <prop key="username">陈永瑞</prop> <prop key="password">陈永瑞</prop> </props> </property> </bean></beans>
-
测试
import com.chen.pojo.Student;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.toString()); /* 结果: Student{ name='陈永瑞', address=Address{address='西安'}, books=[红楼梦, 西游记, 水浒传, 三国演义], hobbys=[听歌, 敲代码, 看电影], card={身份证=1234567, 手机号=1098018}, games=[LOL, COC, BOB], wife='null', info={password=陈永瑞, url=男, driver=2020, username=陈永瑞}} */ }}
p命名和c命名注入
-
User.java :【注意:这里没有有参构造器!】
package com.chen.pojo;public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; }}
-
P命名空间注入 : 需要在头文件中加入约束文件
导入命名空间:xmlns:p="http://www.springframework.org/schema/p"<!--P(属性: setter)命名空间 , 可以通过注入属性的值,设置set方法--><bean id="user1" class="com.chen.pojo.User" p:name="陈永瑞1" p:age="18"/>
-
c 命名空间注入 : 需要在头文件中加入约束文件
导入命名空间:xmlns:c="http://www.springframework.org/schema/c"<!--C(构造: Constructor)命名空间 , 通过构造器注入 constructor-args--><bean id="user2" class="com.chen.pojo.User" c:name="陈永瑞2" c:age="18"/>
发现问题:爆红了,刚才我们没有写有参构造!
**解决:**把有参构造器加上,就知道,c 就是所谓的构造器注入,p是setter方法注入!
-
测试
import com.chen.pojo.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest2 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user1 = context.getBean("user1", User.class); User user2 = context.getBean("user2", User.class); System.out.println(user1); System.out.println(user2); } /* User{name='陈永瑞1', age=18} User{name='陈永瑞2', age=18} */}
1.3、Bean的作用域
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vHvVWKNg-1628569586440)(E:\CYR File\Typora\image\image-20210721165548856.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBgkDrt0-1628569586440)(E:\CYR File\Typora\image\image-20210721165603982.png)]
1、单例模式:(Spring默认机制)
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="user2" class="com.chen.pojo.User" c:name="狂神" c:age="22" scope="singleton"/>
2、原型模式:每次从容器中get的时候,都会产生一个新对象!
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="user2" class="com.chen.pojo.User" c:name="狂神" c:age="22" scope="prototype"/>
3、其余的request、session、application作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
4、测试
import com.chen.pojo.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest3 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user1 = context.getBean("user1", User.class); User user2 = context.getBean("user1", User.class); System.out.println(user1); System.out.println(user2); System.out.println(user1 == user2); System.out.println("======================================"); User user3 = context.getBean("user2", User.class); User user4 = context.getBean("user2", User.class); System.out.println(user3); System.out.println(user4); System.out.println(user3 == user4); } /* User{name='陈永瑞1', age=18} User{name='陈永瑞1', age=18} true ====================================== User{name='陈永瑞2', age=18} User{name='陈永瑞2', age=18} false */}
四、自动装配
1.1、Bean的自动装配
自动装配说明
- 自动装配是使用spring满足bean依赖的一种方法。
- spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring中bean有三种装配机制,分别是:
- 在xml中显式配置;
- 在java中显式配置;
- 隐式的bean发现机制和自动装配(重点);
这里我们主要讲第三种:自动化的装配bean。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置 , 而使用注解 .
测试环境搭建
1、新建一个Maven项目
2、新建两个实体类,Cat Dog 都有一个叫的方法
public class Cat { public void shout() { System.out.println("miao~"); }}public class Dog { public void shout() { System.out.println("wang~"); }}
3、新建一个用户类 People
public class People { private Cat cat; private Dog dog; private String name;}
4、编写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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <bean id="cat" class="com.chen.pojo.Cat"/> <!--<bean id="dog" class="com.chen.pojo.Dog"/>--> <!--<bean id="dog1" class="com.chen.pojo.Dog"/>--> <bean class="com.chen.pojo.Dog"/> <!--<bean id="people" class="com.chen.pojo.People"> <property name="name" value="陈永瑞"/> <property name="dog" ref="dog"/> <property name="cat" ref="cat"/> </bean>--> <!-- byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean id! --> <!--<bean id="people" class="com.chen.pojo.People" autowire="byName"> <property name="name" value="陈永瑞"/> </bean>--> <!-- byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean! --> <bean id="people" class="com.chen.pojo.People" autowire="byType"> <property name="name" value="陈永瑞"/> </bean></beans>
5、测试
import com.chen.pojo.People;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { @Test public void test1() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); People people = context.getBean("people", People.class); people.getCat().shout(); people.getDog().shout(); System.out.println(people.toString()); }}
结果正常输出,环境OK
byName
autowire byName (按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
测试:
1、修改bean配置,增加一个属性 autowire=“byName”
<!-- byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean id!--><bean id="people" class="com.chen.pojo.People" autowire="byName"> <property name="name" value="陈永瑞"/></bean>
2、再次测试,结果依旧成功输出!
3、我们将 cat 的bean id修改为 catXXX
4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结:
当一个bean节点带有 autowire byName的属性时。
- 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
- 去spring容器中寻找是否有此字符串名称id的对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
byType
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常:NoUniqueBeanDefinitionException
测试:
1、将user的bean配置修改一下 : autowire=“byType”
<!-- byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean!--><bean id="people" class="com.chen.pojo.People" autowire="byType"> <property name="name" value="陈永瑞"/></bean>
2、测试,正常输出
3、在注册一个cat 的bean对象!
<bean id="cat" class="com.chen.pojo.Cat"/><bean id="dog" class="com.chen.pojo.Dog"/><bean id="dog1" class="com.chen.pojo.Dog"/><bean class="com.chen.pojo.Dog"/>
4、测试,报错:NoUniqueBeanDefinitionException
5、删掉dog1,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。这就是按照类型自动装配!
1.2、使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
1、在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
2、开启属性注解支持!
<context:annotation-config/>
1.2.1、@Autowired
- @Autowired是按类型自动转配的,不支持id匹配。
- 需要导入 spring-aop的包。
测试:
1、将User类中的set方法去掉,使用@Autowired注解
public class User { @Autowired private Cat cat; @Autowired private Dog dog; private String name; public Cat getCat() { return cat; } public Dog getDog() { return dog; } public String getName() { return name; }}
2、此时配置文件内容
<context:annotation-config/><bean id="dog" class="com.chen.pojo.Dog"/><bean id="cat" class="com.chen.pojo.Cat"/><bean id="people" class="com.chen.pojo.People"/>
3、测试,成功输出结果!
【科普】
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true@Autowired(required = false)private Cat cat;
1.2.2、@Qualifier
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配。
- @Qualifier不能单独使用。
测试实验步骤:
1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog1" class="com.chen.pojo.Dog"/><bean id="dog2" class="com.chen.pojo.Dog"/><bean id="cat1" class="com.chen.pojo.Cat"/><bean id="cat2" class="com.chen.pojo.Cat"/>
2、没有加Qualifier测试,直接报错
3、在属性上添加Qualifier注解
@Autowired@Qualifier(value = "cat2")private Cat cat;@Autowired@Qualifier(value = "dog2")private Dog dog;
测试,成功输出!
1.2.3、@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
实体类:
public class People { @Resource(name = "cat2") private Cat cat; @Resource private Dog dog; private String name;}
applicationContext.xml
<bean id="dog" class="com.chen.pojo.Dog"/><bean id="cat1" class="com.chen.pojo.Cat"/><bean id="cat2" class="com.chen.pojo.Cat"/><bean id="people" class="com.chen.pojo.People"/>
测试:结果OK
配置文件2:applicationContext.xml , 删掉cat2
<bean id="dog" class="com.chen.pojo.Dog"/><bean id="cat1" class="com.chen.pojo.Cat"/>
实体类上只保留注解
@Resourceprivate Cat cat;@Resourceprivate Dog dog;
结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
小结:
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
五、使用注解开发
说明
在spring4之后,想要使用注解形式,必须得要引入aop的包:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ca0KdHj-1628569586442)(E:\CYR File\Typora\image\image-20210722125250477.png)]
在配置文件当中,还得要引入一个context约束:
<?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"></beans>
Bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
1、配置扫描哪些包下的注解
<!--指定注解扫描包--><context:component-scan base-package="com.chen.pojo"/>
2、在指定包下编写类,增加注解
package com.chen.pojo;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;// 等价于 <bean id="user" class="com.chen.pojo.User"/>// @Component 组件@Component("user")// 作用域@Scope("prototype")public class User { // 相当于 <property name="name" value="陈永瑞"/> @Value("陈永瑞") public String name;}
3、测试
@Test public void test1() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = context.getBean("user", User.class); System.out.println(user.name); }
属性注入
使用注解注入属性
1、可以不用提供set方法,直接在直接名上添加@value(“值”)
package com.chen.pojo;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;// 等价于 <bean id="user" class="com.chen.pojo.User"/>// @Component 组件@Component("user")// 作用域@Scope("prototype")public class User { // 相当于 <property name="name" value="陈永瑞"/> @Value("陈永瑞") public String name;}
2、如果提供了set方法,在set方法上添加@value(“值”);
@Component("user")public class User { public String name; @Value("陈永瑞") public void setName(String name) { this.name = name; }}
衍生注解
我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解:
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
自动装配注解
- @Autowired
作用域
@scope:
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
// 等价于 <bean id="user" class="com.chen.pojo.User"/>// @Component 组件@Component("user")// 作用域@Scope("prototype")public class User { // 相当于 <property name="name" value="陈永瑞"/> @Value("陈永瑞") public String name;}
小结
XML与注解比较
- XML可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便,维护复杂
xml与注解整合开发 :推荐最佳实践
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
作用:
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,则注入的值为null
基于Java类进行配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VCEgjiMi-1628569586442)(E:\CYR File\Typora\image\image-20210722140721421.png)]
测试:
1、编写一个实体类
package com.chen.pojo;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Repository;// 说明这个类被spring注册到了容器@Repositorypublic class User { @Value("陈永瑞") private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
2、新建一个config配置包,编写一个MyConfig配置类
package com.chen.config;import com.chen.pojo.User;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;// 这个也会被spring托管,注册到容器中// 代表这是一个配置类,和beans.xml一样@Configuration@ComponentScan("com.chen.pojo")// 引入其他配置类@Import(MyConfig2.class)public class MyConfig { // 注册一个bean,就相当于我们之前写的一个bean标签 // 这个方法的名字,就相当于bean标签中id 的属性 // 这个方法返回值就相当于bean标签中的class属性 @Bean public User getUser() { return new User(); // 就是返回要注入到bean的对象 }}
3、测试
import com.chen.config.MyConfig;import com.chen.pojo.User;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class MyTest { @Test public void test1() { // 如果完全使用了配置类的方式去做,我们就只能通过 AnnotationConfigApplicationContext 上下文获取容器,通过配置类的class对象加载 ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); User getUser = context.getBean("getUser", User.class); System.out.println(getUser.getName()); }}
4、成功输出结果!
导入其他配置如何做呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5vxXXed-1628569586450)(E:\CYR File\Typora\image\image-20210722140407245.png)]
1、我们再编写一个配置类!
@Configuration //代表这是一个配置类public class MyConfig2 {}
2、在之前的配置类中我们来选择导入这个配置类
package com.chen.config;import com.chen.pojo.User;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;// 这个也会被spring托管,注册到容器中// 代表这是一个配置类,和beans.xml一样@Configuration@ComponentScan("com.chen.pojo")// 引入其他配置类@Import(MyConfig2.class)public class MyConfig { // 注册一个bean,就相当于我们之前写的一个bean标签 // 这个方法的名字,就相当于bean标签中id 的属性 // 这个方法返回值就相当于bean标签中的class属性 @Bean public User getUser() { return new User(); // 就是返回要注入到bean的对象 }}
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
六、静态/动态代理模式
1.1、代理模式
为什么要学习代理模式,因为AOP的底层机制就是动态代理!
代理模式:
- 静态代理
- 动态代理
学习aop之前 , 我们要先了解一下代理模式!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CYQlWSbh-1628569586451)(E:\CYR File\Typora\image\image-20210722145746059.png)]
静态代理
静态代理角色分析
-
抽象角色 : 一般使用接口或者抽象类来实现
-
真实角色 : 被代理的角色
-
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
-
客户 : 使用代理角色来进行一些操作 .
代码实现
Rent . java 即抽象角色
//抽象角色:租房public interface Rent { public void rent();}
Host . java 即真实角色
package com.chen.demo01;// 房东public class Host implements Rent { public void rent() { System.out.println("房东要出租房子!"); }}
Proxy . java 即代理角色
package com.chen.demo01;// 代理(中介)public class Proxy implements Rent { private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } // 代理房东出租房 public void rent() { seeHouse(); host.rent(); heTong(); fare(); } // 代理客户看房 public void seeHouse() { System.out.println("中介带你看房!"); } // 收中介费 public void fare() { System.out.println("收中介费!"); } // 签合同 public void heTong() { System.out.println("签租赁合同!"); }}
Client . java 即客户
package com.chen.demo01;public class Client { public static void main(String[] args) { /* Host host = new Host(); host.rent();*/ // 房东要出租房子 Host host = new Host(); // 代理,中介帮房东租房子,但是代理一般有自己的方法 Proxy proxy = new Proxy(host); // 你不用面对房东,直接找中介租房即可 proxy.rent(); }}
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
缺点 :
- 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
静态代理再理解
同学们练习完毕后,我们再来举一个例子,巩固大家的学习!
练习步骤:
1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
//抽象角色:增删改查业务public interface UserService { void add(); void delete(); void update(); void query();}
2、我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人package com.chen.demo02;// 真实对象public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户!"); } public void delete() { System.out.println("删除了一个用户!"); } public void update() { System.out.println("更新了一个用户!"); } public void query() { System.out.println("查询了一个用户!"); }}
3、需求来了,现在我们需要增加一个日志功能,怎么实现!
- 思路1 :在实现类上增加代码 【麻烦!】
- 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
4、设置一个代理类来处理日志!代理角色
package com.chen.demo02;public class UserServiceProxy implements UserService { private UserServiceImpl userService; public void setUserService(UserServiceImpl userService) { this.userService = userService; } public void add() { log("add"); userService.add(); } public void delete() { log("delete"); userService.delete(); } public void update() { log("update"); userService.update(); } public void query() { log("query"); userService.query(); } // 日志方法 public void log(String msg) { System.out.println("[Debug]使用了"+msg+"方法!"); }}
5、测试访问类:
package com.chen.demo02;public class Client { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); UserServiceProxy proxy = new UserServiceProxy(); proxy.setUserService(userService); proxy.add(); proxy.delete(); proxy.query(); proxy.update(); }}
OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
聊聊AOP:纵向开发,横向开发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnKYM0Jl-1628569586452)(E:\CYR File\Typora\image\image-20210722150115851.png)]
1.2、动态代理
-
动态代理的角色和静态代理的一样 。
-
动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的。
-
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
-
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理–cglib
- 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
- 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
JDK的动态代理需要了解两个类:
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
【InvocationHandler:调用处理程序】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzzkTFi1-1628569586453)(E:\CYR File\Typora\image\image-20210722154034814.png)]
Object invoke(Object proxy, 方法 method, Object[] args);//参数//proxy - 调用该方法的代理实例//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
【Proxy : 代理】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yb4UuBi0-1628569586453)(E:\CYR File\Typora\image\image-20210722154153130.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-43FP2n6W-1628569586454)(E:\CYR File\Typora\image\image-20210722154205813.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WqdsULPM-1628569586455)(E:\CYR File\Typora\image\image-20210722154258368.png)]
//生成代理类public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);}
代码实现
抽象角色和真实角色和之前的一样
Rent . java 即抽象角色
//抽象角色:租房public interface Rent { public void rent();}
Host . java 即真实角色
//真实角色: 房东,房东要出租房子public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); }}
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } // proxy : 代理类 method : 代理类的调用处理程序的方法对象. // 处理代理实例上的方法调用并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); //核心:本质利用反射实现! Object result = method.invoke(rent, args); fare(); return result; } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); }}
Client . java
//租客public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理实例的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setRent(host); //将真实角色放置进去! Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类! proxy.rent(); }}
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、
深化理解
我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
public class ProxyInvocationHandler implements InvocationHandler { private Object target; public void setTarget(Object target) { this.target = target; } //生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // proxy : 代理类 // method : 代理类的调用处理程序的方法对象. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object result = method.invoke(target, args); return result; } public void log(String methodName){ System.out.println("执行了"+methodName+"方法"); }}
测试!
package com.chen.demo04;import com.chen.demo02.UserService;import com.chen.demo02.UserServiceImpl;public class Client { public static void main(String[] args) { // 真实角色 UserServiceImpl userService = new UserServiceImpl(); // 代理角色: 不存在、 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userService); // 设置要代理的对象 // 动态生成代理类 UserService proxy = (UserService) pih.getProxy(); proxy.delete(); }}
测试,增删改查,查看结果!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKqQwzyE-1628569586455)(E:\CYR File\Typora\image\image-20210722154547085.png)]
动态代理的好处
静态代理有的它都有,静态代理没有的,它也有!
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务
- 一个动态代理可以代理多个类,代理的是接口!
七、面向切面AOP
什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zkq7M94K-1628569586456)(E:\CYR File\Typora\image\image-20210722171315215.png)]
Aop在Spring中的作用
提供声明式事务、允许用户自定义切面;
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zmey8JeA-1628569586456)(E:\CYR File\Typora\image\image-20210722171503548.png)]
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNyEw7Dg-1628569586457)(E:\CYR File\Typora\image\image-20210722171557236.png)]
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .
使用Spring实现Aop
【重点】使用AOP织入,需要导入一个依赖包!
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version></dependency>
第一种方式:通过 Spring API 实现
首先编写我们的业务接口和实现类
public interface UserService { void add(); void delete(); void update(); void query();}public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户!"); } public void delete() { System.out.println("删除了一个用户!"); } public void update() { System.out.println("更新了一个用户!"); } public void query() { System.out.println("查询了一个用户!"); }}
然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
package com.chen.log;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class BeforeLog implements MethodBeforeAdvice { // method:要执行的目标对象方法 // args: 参数 // target:目标对象 public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass()+"的"+method.getName()+"执行了"); }}package com.chen.log;import org.springframework.aop.AfterReturningAdvice;import java.lang.reflect.Method;public class AfterLog implements AfterReturningAdvice { // returnValue:返回值 public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue); }}
最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .
<?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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 注册bean --> <bean id="userService" class="com.chen.service.UserServiceImpl"/> <bean id="beforeLog" class="com.chen.log.BeforeLog"/> <bean id="afterLog" class="com.chen.log.AfterLog"/> <!-- 方式一:使用原生Spring API接口 --> <!-- 配置AOP 需要导入aop的约束 --> <aop:config> <!-- 切入点 expression表达式:execution(要执行的位置 * * * * *)--> <aop:pointcut id="pointcut" expression="execution(* com.chen.service.UserServiceImpl.*(..))"/> <!--执行环绕增加--> <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config></beans>
测试
import com.chen.service.UserService;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { @Test public void Test1() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 动态代理 代理的是接口 UserService userService = context.getBean("userService", UserService.class); userService.add(); }}
Aop的重要性 : 很重要 ,一定要理解其中的思路 ,主要是思想的理解这一块 。
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
第二种方式:自定义类来实现Aop
目标业务类不变依旧是userServiceImpl
public interface UserService { void add(); void delete(); void update(); void query();}public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户!"); } public void delete() { System.out.println("删除了一个用户!"); } public void update() { System.out.println("更新了一个用户!"); } public void query() { System.out.println("查询了一个用户!"); }}
第一步 : 写我们自己的一个切入类
package com.chen.diy;// 自定义切入点public class DiyPointCut { public void before() { System.out.println("========方法执行前========"); } public void after() { System.out.println("========方法执行后========"); }}
去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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 方式二:自定义类 --> <bean id="diy" class="com.chen.diy.DiyPointCut"/> <aop:config> <!--自定义切面 ref 要引用的类--> <aop:aspect ref="diy"> <!--切入点--> <aop:pointcut id="point" expression="execution(* com.chen.service.UserServiceImpl.*(..))"/> <!--通知--> <aop:before pointcut-ref="point" method="before"/> <aop:after pointcut-ref="point" method="after"/> </aop:aspect> </aop:config></beans>
测试:
import com.chen.service.UserService;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { @Test public void Test1() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 动态代理 代理的是接口 UserService userService = context.getBean("userService", UserService.class); userService.add(); }}
第三种方式:使用注解实现
第一步:编写一个注解实现的增强类
package com.chen.diy;// 方式三:使用注解方式实现AOPimport org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspect // 标注这个类是一个切面public class AnnotationPointCut { @Before("execution(* com.chen.service.UserServiceImpl.*(..))") public void before() { System.out.println("========方法执行前========"); } @After("execution(* com.chen.service.UserServiceImpl.*(..))") public void after() { System.out.println("========方法执行后========"); } // 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点 @Around("execution(* com.chen.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); System.out.println("签名:"+jp.getSignature()); //执行目标方法proceed Object proceed = jp.proceed(); System.out.println("环绕后"); System.out.println(proceed); }}
第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!-- 方式三:使用注解 --> <bean id="annotationPointCut" class="com.chen.diy.AnnotationPointCut"/> <!-- 开启注解支持 --> <!-- 动态代理 JDK(默认proxy-target-class="false") cglib(proxy-target-class="true") --> <aop:aspectj-autoproxy proxy-target-class="false"/>
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
八、整合Mybatis
步骤
导入相关jar包:
- junit
- mybatis
- mysql数据库
- spring相关
- aop织入器
- mybatis-spring整合包【重点】在此还导入了lombok包。
- 配置Maven静态资源过滤问题!
<?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"> <parent> <artifactId>Spring-Study</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-9-mybatis</artifactId> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <!--Spring操作数据库的话,还需要一个spring-jdbc--> <!-- spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <!-- aop --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <!-- mybatis-spring整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build></project>
编写配置文件:
测试:
1.1、回忆mybatis
编写pojo实体类:
@Datapublic class User { private int id; private String name; private String pwd;}
编写实现mybatis的配置文件:
<?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核心配置文件--><configuration> <typeAliases> <package name="com.chen.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="199819"/> </dataSource> </environment> </environments> <mappers> <mapper resource="sqlMapper/UserMapper.xml"/> </mappers></configuration>
编写UserMapper接口:
public interface UserMapper { List<User> selectUser();}
编写UserMapper.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.chen.mapper.UserMapper"> <select id="selectUser" resultType="user"> select * from user; </select></mapper>
测试:
import com.chen.mapper.UserMapper;import com.chen.pojo.User;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 MyTest { @Test public void selectUser() throws IOException { String resource = "Mybatis-Config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.selectUser(); for (User user: userList){ System.out.println(user); } sqlSession.close(); }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b3RhHGSC-1628569586458)(E:\CYR File\Typora\image\image-20210723115353314.png)]
1.2、Mybatis-Spring
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
文档链接:http://mybatis.org/spring/zh/index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPIB2zvG-1628569586458)(E:\CYR File\Typora\image\image-20210723121538691.png)]
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:
<!-- mybatis-spring整合包 --><dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version></dependency>
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /></bean>
注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /></bean>
现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:
public class UserDaoImpl implements UserDao { private SqlSession sqlSession; public void setSqlSession(SqlSession sqlSession) { this.sqlSession = sqlSession;} public User getUser(String userId) { return sqlSession.getMapper...;}}
按下面这样,注入 SqlSessionTemplate:
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl"> <property name="sqlSession" ref="sqlSession" /></bean>
整合实现一
1、引入Spring配置文件spring-dao.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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"></beans>
2、配置数据源替换mybaits的数据源
<!--配置数据源替换mybatis的数据源--> <!--DataSource:使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid 我们这里使用Spring提供的JDBC:--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="199819"/> </bean>
3、配置SqlSessionFactory,关联MyBatis
<!--配置SqlSessionFactory,关联MyBatis--><!--sqlSessionFactory--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!--关联mybatis配置文件--> <property name="configLocation" value="classpath:mybatis-Config.xml"/> <property name="mapperLocations" value="classpath:sqlMapper/*.xml"/></bean>
4、注册sqlSessionTemplate,关联sqlSessionFactory;
<!--注册sqlSessionTemplate,关联sqlSessionFactory--><!--SqlSessionTemplate:就是我们使用的sqlSession--><bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能使用构造器注入sqlSessionFactory,因为它没有set方法--> <constructor-arg index="0" ref="sqlSessionFactory" /></bean>
5、增加Mapper接口的实现类;私有化sqlSessionTemplate
package com.chen.mapper;import com.chen.pojo.User;import org.mybatis.spring.SqlSessionTemplate;import java.util.List;public class UserMapperImpl implements UserMapper { //我们的所有操作,都使用sqlSession来执行,在原来,现在都使用SqlSessionTemplate private SqlSessionTemplate sqlSession; // set注入 public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); }}
6、注册bean实现
<!-- set方法注入 --><bean id="userMapper" class="com.chen.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/></bean>
7、测试
@Test public void test2() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); for (User user : userMapper.selectUser()) { System.out.println(user); } }
结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!
<?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> <typeAliases> <package name="com.chen.pojo"/> </typeAliases></configuration>
整合实现二
mybatis-spring1.2.3版以上的才有这个。
官方文档截图 :
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0IQ21IDD-1628569586459)(E:\CYR File\Typora\image\image-20210723125301109.png)]
测试:
1、将我们上面写的UserDaoImpl修改一下
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper { /*//我们的所有操作,都使用sqlSession来执行,在原来,现在都使用SqlSessionTemplate private SqlSessionTemplate sqlSession; // set注入 public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); }*/ public List<User> selectUser() { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.selectUser(); }}
2、修改bean的配置
<bean id="userMapper" class="com.chen.mapper.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /></bean>
3、测试
@Test public void test2() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); for (User user : userMapper.selectUser()) { System.out.println(user); } }
总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!
九、声明式事务
回顾事务
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
事务四个属性ACID
-
原子性(atomicity)
-
- 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
-
一致性(consistency)
-
- 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
-
隔离性(isolation)
-
- 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
-
持久性(durability)
-
- 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
测试
将上面的代码拷贝到一个新项目中
在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;
//添加一个用户int addUser(User user);//根据id删除用户int deleteUser(int id);
mapper文件,我们故意把 delete 写错 写成deletes,测试!
<insert id="addUser" parameterType="com.kuang.pojo.User">insert into user (id,name,pwd) values (#{id},#{name},#{pwd})</insert><delete id="deleteUser" parameterType="int">deletes from user where id = #{id}</delete>
编写接口的实现类,在实现类中,我们去操作一波
package com.chen.mapper;import com.chen.pojo.User;import org.mybatis.spring.support.SqlSessionDaoSupport;import java.util.List;public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper { //增加一些操作 public List<User> selectUser() { User user = new User(4,"小明","123456"); UserMapper mapper = getSqlSession().getMapper(UserMapper.class); mapper.addUser(user); mapper.deleteUser(4); return mapper.selectUser(); } //新增 public int addUser(User user) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.addUser(user); } //删除 public int deleteUser(int id) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.deleteUser(id); }}
测试
import com.chen.mapper.UserMapper;import com.chen.pojo.User;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.util.List;public class MyTest { @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper mapper = (UserMapper) context.getBean("userMapper"); List<User> user = mapper.selectUser(); for (User user1 : user) { System.out.println(user1); } }}
报错:sql异常,delete写错了
结果 :插入成功!
没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!
以前我们都需要自己手动管理事务,十分麻烦!
但是Spring给我们提供了事务管理,我们只需要配置即可;
Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
- 一般情况下比编程式事务好用。
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
- 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx"http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd">
事务管理器
- 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
- 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /></bean>
配置好事务管理器后我们需要去配置事务的通知
<!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--配置哪些方法使用什么样的事务,配置事务的传播特性--> <!--<tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="search*" propagation="REQUIRED"/> <tx:method name="get" read-only="true"/>--> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
配置AOP
导入aop的头文件!
<!--配置aop织入事务--><aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.chen.dao.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/></aop:config>
进行测试
删掉刚才插入的数据,再次测试!
public class MyTest { @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper mapper = (UserMapper) context.getBean("userMapper"); List<User> user = mapper.selectUser(); for (User user1 : user) { System.out.println(user1); } }}
思考问题?
为什么需要配置事务?
- 如果不配置,就需要我们手动提交控制事务;
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!