Spring5框架
Spring5框架
一、入门
1、简介
- Spring是春天的意思,意味着给软件行业带来了春天!
- 2002年,首次推出了Spring框架的雏形:interface21框架
- Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。
- 作者是Rod Johnson,在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位。
- spring理念:使现有的技术更加容易的使用,本身是一个大杂烩,整合了现有的框架。
- SSH:Struct2+Spring+Hibernate
- SSM:SpringMvc+Spring+Mabatis
需要的依赖的maven:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.3</version> </dependency>
2、优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非侵入式的框架
- Spring的两大特性:控制反转(IOC)、面向切面编程(AOP)
- 支持事务的处理,对框架整合非常支持。
总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。
3、Spring的七大模块
4、扩展
- Spring Boot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速地开发一个单体微服务
- 约定大于配置
- Spring Cloud
- SpringCloud是基于SpringBoot实现的
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring和SpringMVC。
Spring弊端:发展太久了之后,违背了原来的理念,配置变得十分复杂!
二、控制反转(IOC)
1、IOC理论推导
我们之前写的代码是这样的:
Dao层: public class UserDaoImpl implements UserDao { @Override public void getUser() { System.out.println("获取用户信息...."); } } service层: public class UserServiceImpl implements UserService { UserDao userDao = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); } } 用户(客户端client): public class UserTest { public static void main(String[] args) { UserService userService = new UserServiceImpl(); userService.getUser(); } }
其中,service层通过UserDao userDao = new UserDaoImpl()
调用Dao层,Dao接口是通过程序员敲代码的形式直接new出来的Dao接口的实现类,也就是说现在是程序主动创建对象,创建对象的控制权在程序员手上的。
现在我们在service层加入一个setter方法,将Dao接口通过setter注入的形式进行创建。
public class UserServiceImpl implements UserService { UserDao userDao ; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void getUser() { userDao.getUser(); } }
那么用户端就可以通过调用service层的setter方法主动选择Dao接口创建的实现类。
public class UserTest { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(new UserDaoImpl()); //userService.setUserDao(new UserDaoImpl1()); //userService.setUserDao(new UserDaoImpl2()); //userService.setUserDao(new UserDaoImpl3()); //......................................... userService.getUser(); } }
所以,我们使用了setter注入的方法之后,程序不再具有主动性,而是变成了被动的接受对象。也就是说,现在创建对象的权利在用户的手中,而不是程序员去通过敲代码来“写死”的创建对象。
这种思想,从本质上解决了我们的问题,程序员不再去管理对象的创建了,而是交给了客户端。系统的耦合性大大降低,可以更加专注的在实现业务上,这就是IOC(控制反转)的原型。(控制是创建对象的主动权)
2、IOC本质
控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IOC 的一种方法。在没有IOC的程序中,我们使用面向对象的编程,对象的创建和对象之间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓的控制反转就是:获得依赖对象的方式反转了。
3、Spring的使用
xml配置方式
1.在Maven中引入spring-context依赖包,此时Maven会将spring的相关包如core、aop、beans、expression包同步下载。
<!-- spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency>
2.在项目中配置spring的核心配置文件(applicationContext.xml<文件名任意>),一般是建在resoures目录下。(配置元数据)
<?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标签代表着一个java对象,下面的意思就是把class下的类:com.jsx.Dao.UserDaoImpl交给Spring进行管理,由Spring容器负责创建该类的对象--> <!-- id:创建对象的一个标识,创建该对象时,创建者由id来从Spring容器中决定要创建哪个对象,id在该spring容器中必须唯一 class:要给spring容器托管的类 --> <bean id="userDao" class="com.jsx.Dao.UserDaoImpl"> <!-- collaborators and configuration for this bean go here --> </bean> </beans>
- 使用spring容器创建并使用对象
在我们的客户端(main方法中),写入以下代码:
/** * 启动spring环境并初始化spring容器 * ClassPathXmlApplicationContext是ApplicationContext接口基于XML的一个实现类 */ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器中获得对象,根据容器中的id标识名获得 UserDao userDao = (UserDao) applicationContext.getBean("userDao"); userDao.addUser();
注解编程式
创建一个配置类,任意一个类,但要使用@Configuration
@Configuration public class AppConfig { }
在配置类中定义方法,用于创建Bean对象,在方法上要加入@Bean注解
package com.jsx.config; import com.jsx.dao.UserDao; import com.jsx.dao.impl.UserDaoImpl; import com.jsx.service.UserService; import com.jsx.service.impl.UserServiceImpl; import org.springframework.context.annotation.*; /** * @ClassName: Appconfig * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/10 15:33 * @Version: 1.0 **/ @Configuration public class AppConfig { @Lazy //懒加载 @Scope("prototype") //Bean的作用域 @Bean //该注解只能在配置类中的方法中使用,表示将方法的返回值交给spring进行管理的一个Bean对象 //Bean注解默认的对象名为方法名,我们可以设置对象名 //@Bean("userDao") public UserDao userDao() { return new UserDaoImpl(); } @Bean public UserService userService(UserDao userDao) { //使用传参的方式进行依赖注入 UserServiceImpl userService = new UserServiceImpl(); //但是这里又违背了开闭原则 userService.setUserDao(userDao); return new UserServiceImpl(); } }
(3)最方便的实现方法
依赖注入的时候使用组件开发与自动装配的注解来实现,这样就可以解决new的问题了
UserDaoImpl类
package com.jsx.dao.impl; import com.jsx.dao.UserDao; import org.springframework.stereotype.Component; import java.util.List; /** * @ClassName: UserDaoImpl * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/10 14:09 * @Version: 1.0 **/ @Component("userDao") //组件注解:将当前类的对象交给Spring容器进行管理,组件id默认为类名,可以给该组件设置id public class UserDaoImpl implements UserDao { public UserDaoImpl() { System.out.println("创建UserDao对象......"); } public void addUser() { System.out.println("执行addUser方法..."); } public void delUser() { System.out.println("执行delUser方法..."); } public List queryUser() { System.out.println("执行queryUser方法..."); return null; } public void init() { System.out.println("初始化UserDao...."); } public void destroy() { System.out.println("销毁UserDao...."); } }
UserServiceImpl类
package com.jsx.service.impl; import com.jsx.dao.UserDao; import com.jsx.dao.impl.UserDaoImpl; import com.jsx.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.util.List; /** * @ClassName: UserServiceImpl * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/10 14:11 * @Version: 1.0 **/ @Service("userService") public class UserServiceImpl implements UserService { @Autowired //通过该注解可以实现自动装配,它的作用相当于set方法注入,有了它就可以省略set方法了 private UserDao userDao; //public void setUserDao(UserDao userDao) { // this.userDao = userDao; // } public void addUser() { userDao.addUser(); } public void delUser() { userDao.delUser(); } public List queryUser() { userDao.queryUser(); return null; } }
还有一个注解叫做
@Qualifier
,它不能单独使用,它可以与@Autowired
配合,根据名称进行自动装配。1.当Spring容器中有针对于同一个变量的多个bean
<bean id="dog1" class="com.xinzhi.entity.Dog"/> <bean id="dog2" class="com.xinzhi.entity.Dog"/> <bean id="cat1" class="com.xinzhi.entity.Cat"/> <bean id="cat2" class="com.xinzhi.entity.Cat"/>
2.这时候如果没有加
@Qualifier
,只有@Autowired
直接报错。@Autowired private Cat cat; @Autowired private Dog dog;
3.加上
@Qualifier
后,可以根据注解中的值,通过Bean的id寻找对应bean。@Autowired @Qualifier(value = "cat2") private Cat cat; @Autowired @Qualifier(value = "dog2") private Dog dog;
配置类
@ComponentScan({"com.jsx.dao","com.jsx.service"}) //组件扫描器,会扫描指定包下的组件注解 @Configuration public class AppConfig { }
依赖注入
创建实体类
package com.jsx.model; /** * @ClassName: User * @Description: TODO * @Author: JiaShiXi * @Date: 2021/2/9 18:31 * @Version: 1.0 **/ public class User { private String username; //用户名 private int score; //积分 //无参构造 public User() { System.out.println("调用无参构造...."); } //有参构造 public User(String username, int score) { this.username = username; this.score = score; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } @Override public String toString() { return "User{" + "username='" + username + '\'' + ", score=" + score + '}'; } }
第一种方式:构造器注入
//1.无参构造 <bean id="user" class="com.jsx.model.User"></bean> //2.有参构造(参数的个数对应着<constructor-arg>标签的个数) //根据有参构造函数的参数名称注入(重要) <bean id="user1" class="com.jsx.model.User"> <constructor-arg name="username" value="小贾"/> <constructor-arg name="score" value="50"/> </bean> //根据有参构造函数的参数顺序注入(重要) <bean id="user3" class="com.jsx.model.User"> <constructor-arg index="0" value="小贾"/> <constructor-arg index="1" value="50"/> </bean> //根据有参构造函数的参数类型注入(了解) <bean id="user2" class="com.jsx.model.User"> <constructor-arg type="java.lang.String" value="小贾"/> //对应的java代码中的类型必须是Integer,不能是int,否则会报UnsatisfiedDependencyException异常 <constructor-arg type="java.lang.Integer" value="50"/> </bean> //直接注入(也是按照参数的顺序)(了解) <bean id="user4" class="com.jsx.model.User"> <constructor-arg value="小贾"/> <constructor-arg value="50"/> </bean>
如果要通过构造方法注入参数,并且为有参的构造方法,必须在实体类中定义该有参方法,在配置文件中对应的bean必须对应该有参构造的参数个数。
第二种方式:set方法注入
<bean id="user5" class="com.jsx.model.User"> <property name="score" value="20"/> <property name="username" value="小贾"/> </bean>
使用set方法注入在对应的实体类中必须定义对应变量的set方法,根据<property>的个数来注入对应个数的变量,可以注入0个、1个....参数。调用的构造方法是无参构造。
value
属性注入的是基本类型,如果是引用类型(自定义的类型)要使用ref
属性来注入。
set方法除了注入这些普通的类型外,还有引用类型以及各种数组、集合等类型,下面通过一个例子来说明这些类型应该如何进行注入。
Address类
package com.jsx.model; /** * @ClassName: Address * @Description: TODO * @Author: JiaShiXi * @Date: 2021/2/9 19:54 * @Version: 1.0 **/ public class Address { private String addressInfo;//地址信息 public void setAddressInfo(String addressInfo) { this.addressInfo = addressInfo; } @Override public String toString() { return "Address{" + "addressInfo='" + addressInfo + '\'' + '}'; } }
People类
package com.jsx.model; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; /** * @ClassName: People * @Description: TODO * @Author: JiaShiXi * @Date: 2021/2/9 19:54 * @Version: 1.0 **/ public class People { private String name; //姓名 private Address address; //地址 private String[] hobbies; //爱好 private List<String> duties; //职务 private Map<String,String> familyTies; //家庭关系 private Set<String> carts; //购物车商品 private Properties workExperience; //工作经历 private String son; //儿子 public void setName(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public void setHobbies(String[] hobbies) { this.hobbies = hobbies; } public void setDuties(List<String> duties) { this.duties = duties; } public void setFamilyTies(Map<String, String> familyTies) { this.familyTies = familyTies; } public void setCarts(Set<String> carts) { this.carts = carts; } public void setWorkExperience(Properties workExperience) { this.workExperience = workExperience; } public void setSon(String son) { this.son = son; } @Override public String toString() { return "People{" + "name='" + name + '\'' + ", address=" + address + ", hobbies=" + Arrays.toString(hobbies) + ", duties=" + duties + ", familyTies=" + familyTies + ", carts=" + carts + ", workExperience=" + workExperience + ", son='" + son + '\'' + '}'; } }
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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--将Address对象交给spring容器进行管理--> <bean id="address" class="com.jsx.model.Address"> <!--通过set方法注入的方式给addressInfo变量赋值--> <property name="addressInfo" value="山东省泰安市"/> </bean> <!--将People对象交给spring容器进行管理--> <bean id="people" class="com.jsx.model.People"> <!--String类型,使用value属性--> <property name="name" value="贾师熹"/> <!--引用(自定义)类型,使用ref属性引用id为address的bean对象--> <property name="address" ref="address"/> <!--数组注入,property*1>array*1>value*n--> <property name="hobbies"> <array> <value>唱歌</value> <value>跳舞</value> <value>弹琴</value> <value>画画</value> </array> </property> <!--list集合注入,property>list>value--> <property name="duties"> <list> <value>学生</value> <value>学生1</value> <value>学生2</value> <value>学生n</value> </list> </property> <!--map集合注入,property>map>entry(key,value)--> <property name="familyTies"> <map> <entry key="father" value="贾XX"/> <entry key="son" value="贾XX"/> </map> </property> <!--set集合注入,property>set>value--> <property name="carts"> <set> <value>商品1</value> <value>商品2</value> <value>商品3</value> <value>商品n</value> </set> </property> <!--Properties注入,property>props>prop(key)--> <property name="workExperience"> <props> <prop key="职务1"></prop> <prop key="职务2"></prop> <prop key="职务3"></prop> </props> </property> <!--null注入--> <property name="son"> <null /> </property> </bean> </beans>
测试方法
@Test public void peopleTest(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("PeopleApplication.xml"); People people = (People) applicationContext.getBean("people"); System.out.println(people); }
测试结果
People{name='贾师熹', address=com.jsx.model.Address@4470f8a6, hobbies=[唱歌, 跳舞, 弹琴, 画画], duties=[学生, 学生1, 学生2, 学生n], familyTies={father=贾XX, son=贾XX}, carts=[商品1, 商品2, 商品3, 商品n], workExperience={职务3=, 职务2=, 职务1=}, son='null'}
Spring对象管理
1.Spring管理对象的创建时机
默认情况下,spring容器一创建,由spring管理的所有对象同步创建。
可以通过修改相关配置修改Spring受管对象的创建时机,通过配置lazy属性可以让spring的受管对象进行
懒加载(在使用时才创建)
创建。设置lazy的方式有两种:
设置全局对象懒加载(Spring中所有的受管对象都使用懒加载机制)
<!--在beans标签中配置default-lazy-init="true"--> <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" default-lazy-init="true">
设置某个对象懒加载
<!--在bean标签中加入lazy-init="true"--> <bean id="address" class="com.jsx.model.Address" lazy-init="true"> <!--通过set方法注入的方式给addressInfo变量赋值--> <property name="addressInfo" value="山东省泰安市"/> </bean>
2.Spring管理对象的作用域(Bean的作用域)
Spring IOC容器创建一个Bean实例时,可以为Bean指定实例的作用域,作用域包括singleton(单例模式)、prototype(原型模式)、request(HTTP请求)、seesion(会话)、global-session(全局会话)。
(1)singleton
当一个bean的作用域为singleton,那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则会只返回bean的同一实例。
singleton是单例类型,每次获取到的都是同一个对象。
注意:singleton作用域是Spring中的缺省作用域。
要在XML中将bean定义成singleton,可以这样配置:
<bean id="user" class="com.jsx.model.User" scope="singleton"></bean>
(2)prototype
当一个bean的作用域为prototype时,表示一个bean定义对应多个对象实例,每从spring容器中获取该对象时便新创建一个实例。
prototype作用域的bean会导致在每次对该bean请求(将其注入到另外一个bean,或者以程序的方式调用容器的getBean()方法)时,都会创建一个新的bean实例。
prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会创建一个对象,而且我们每次获取到的对象都不是同一个对象。
根据经验,对有状态的bean应该使用prototype作用域,而对于无状态的bean则应该使用singleton作用域。
在XML中将bean定义成prototype,可以这样配置:
<bean id="user" class="com.jsx.model.User" scope="prototype"></bean>
其他几种Bean的作用域在Spring MVC中才会用到,在这里我们先不做讨论。
4、注解开发
1.在java代码中可以使用@Autowire或者@Resource注解方式进行装配,这两个注解的区别是:
@Autowire 默认按照类型装配,默认情况下它要求依赖对象必须存在如果允许为null,可以设置它required属性为false,如果我们想使用按照名称装配,可 以结合@Qualifier注解一起使用;
@Resource默认按照名称装配,当找不到与名称匹配的bean才会按照类型装配,可以通过name属性指定,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象(注意:如果没有指定name属性,并且按照默认的名称仍然找不到依赖的对象时候,会回退到按照类型装配,但一旦指定了name属性,就只能按照名称装配了)
2.分层开发
- @Controller
@Service
@Repository - dao层,注意@Repository要加在实现类上
- service层,注意@Service要加在实现类上
- controller层,注意@Controller要加在类上
都等同于@Component,但是因为分层的原因,使用不同的注解可读性更高
3.组件开发
@Component:将当前类的对象交给Spring容器进行管理
@Value:加在普通属性上给其赋值@Component public class Student { @Value("张三") private String name; @Autowired private Address address; }
4.配置文件
- @Bean:将返回值交给Spring容器进行管理
@Configuration:标明当前类是一个配置类
@Configuration public class AnimalConfig { @Bean public Mouse mouse(){ Mouse mouse = new Mouse(); mouse.setAge(12); mouse.setName("jerry"); return mouse; } //参数是你要注入的其他的bean @Bean public Cat cat(Mouse mouse){ Cat cat = new Cat(); cat.setAge(5); cat.setName("Tom"); cat.setFood(mouse); return cat; } }
三、Spring测试环境
1.添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.6.RELEASE</version> </dependency>
2.测试用例
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:beans.xml") public class Test2 { @Resource private MaleActor maleActor; @Test public void testLiftCircle(){ maleActor.work(); } }
@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,以便在测试开始的时候自动创建Spring的应用上下文(ApplicationContext)
四、Bean的生命周期
生命周期不管在哪个概念里都是说明了一个 东西从生到死的一个过程。
在这个bean的各个阶段加入一些内容:
public class User implements Serializable { private int id; private String username; private String password; public User() { System.out.println("--------构造--------"); } public int getId() { return id; } public void setId(int id) { System.out.println("--------注入--------"); this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public void init(){ System.out.println("--------init初始化--------"); } public void destroy(){ System.out.println("--------destroy销毁--------"); } @Override public String toString() { System.out.println("--------使用了--------"); return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
通过结果来观察:
--------构造-------- --------注入-------- --------init初始化-------- --------使用了-------- User{id=1, username='zhangsna', password='123'} 10:16:45.035 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@2ff4f00f, started on Sat May 30 10:16:44 CST 2020 --------destroy销毁--------
有两个方法可以实现初始化前和初始化后的操作:
public class BeanHandler implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("--------初始化前--------"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("--------初始化后--------"); return bean; } }
--------构造-------- --------注入-------- --------初始化前-------- --------init初始化-------- --------初始化后-------- --------使用了-------- User{id=1, username='zhangsna', password='123'} 10:18:33.163 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@2ff4f00f, started on Sat May 30 10:18:32 CST 2020 --------destroy销毁--------
五、面向切面编程(AOP)
1、代理模式
AOP的底层实现原理就是代理模式的实现,我们首先来学习一下代理模式。
代理模式分为静态代理和动态代理两种。
定义
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
优点
中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口
。
开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能
,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现
,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
静态代理
这里我们以租房中介为例:
1.创建服务类接口(共同的需求:卖房子)
package com.jsx.proxy; /** * @InterfaceName: BuyHouse * @Description: TODO 公共的需求接口 * @Author: JiaShiXi * @Date: 2021/2/11 20:00 * @Version: 1.0 **/ public interface SellHouse { //代理与委托者共同的动作:卖房子 public void sellHouse(); }
2.委托者实现服务接口(卖家)
package com.jsx.proxy; /** * @ClassName: Seller * @Description: TODO 卖家——委托者 * @Author: JiaShiXi * @Date: 2021/2/11 20:04 * @Version: 1.0 **/ public class Seller implements SellHouse{ @Override public void sellHouse() { System.out.println("卖家卖房子..."); } }
3.创建代理类(房屋中介)
package com.jsx.proxy; /** * @ClassName: HouseProxy * @Description: TODO 房屋中介——代理类 * @Author: JiaShiXi * @Date: 2021/2/11 20:06 * @Version: 1.0 **/ public class HouseProxy implements SellHouse { private Seller seller; //帮卖家代理 public HouseProxy(Seller seller) { this.seller = seller; } @Override public void sellHouse() { before(); seller.sellHouse();//实际上调用的还是委托者的方法 after(); } public void before(){ System.out.println("卖房前的准备..."); } public void after(){ System.out.println("卖房之后的操作..."); } }
4.创建测试类(买家)
package com.jsx.proxy; /** * @ClassName: Custom * @Description: TODO * @Author: JiaShiXi * @Date: 2021/2/11 20:11 * @Version: 1.0 **/ public class Custom { public static void main(String[] args) { Seller seller = new Seller(); HouseProxy houseProxy = new HouseProxy(seller); //买房子的客户只需要直接和房屋中介打交道 houseProxy.sellHouse();//房屋中介帮我们做事 } }
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建
。
1.创建委托者(卖家类)和服务接口(卖房接口)
2.创建动态处理器,处理器中实现获取代理的方法,重写invoke方法
package com.jsx.proxy.dynamicmethod; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @ClassName: DynamicProxyHandler * @Description: TODO 动态处理器 * @Author: JiaShiXi * @Date: 2021/2/11 20:23 * @Version: 1.0 **/ /** * InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口, * 每一个proxy代理实例都有一个关联的调用处理程序; * 在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。 */ public class DynamicProxyHandler implements InvocationHandler { private Object seller; //实现公共的服务接口的卖家(委托者) public void setSellHouse(Object seller) { this.seller = seller; } //生成代理类 public Object getProxy(){ /** * 第一个参数:this.getClass().getClassLoader(),使用当前类对象的classloader对象来加载我们的代理对象 * 第二个参数:seller.getClass().getInterfaces(),这里为代理类提供的接口是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法 * 第三个参数:this,当前的handler对象,我们将代理对象关联到上面的InvocationHandler对象上 */ Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), seller.getClass().getInterfaces(), this); return o; } /** * * @param o 代理类代理的真实代理对象com.sun.proxy.$Proxy0 * @param method 我们所要调用某个对象真实的方法的Method对象 * @param objects 指代真实对象方法传递的参数 * @return * @throws Throwable */ @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("before"); //执行委托者方法前的操作 Object invoke = method.invoke(seller, objects); System.out.println("after"); //执行委托者方法后的操作 return invoke; } }
3.编写测试类(买家)
package com.jsx.proxy.dynamicmethod; /** * @ClassName: Custom * @Description: TODO * @Author: JiaShiXi * @Date: 2021/2/11 20:54 * @Version: 1.0 **/ public class Custom { public static void main(String[] args) { //创建真实对象(委托者) Seller seller = new Seller(); //创建代理角色 //1.创建动态处理器 DynamicProxyHandler handler = new DynamicProxyHandler(); handler.setSellHouse(seller); //2.通过动态处理器来获取代理对象 SellHouse proxy = (SellHouse) handler.getProxy(); //通过代理对象来调用方法 proxy.sellHouse(); } }
运行结果:
before 卖家卖房子... after
注意Proxy.newProxyInstance()方法接受三个参数:
ClassLoader loader
:指定当前目标对象使用的类加载器,获取加载器的方法是固定的Class<?>[] interfaces
:指定目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler:
指定动态处理器,
执行目标对象的方法时,会触发事件处理器的方法
2、AOP简介
什么是AOP?
- AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志、事务、权限等等。
- 在不改变原有逻辑的基础上,增加一些额外的功能。
代理
也是这个功能。 - AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向的散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其它类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处无关的代码被称为横切(Cross Cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的复用。
- AOP技术恰恰相反,它利用一种“横切”的技术,配解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
- 使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
总结:
就是有这样的需求,需要在某一些类中,增加很多统一的代码,比如日志、事务、权限等等。
- 一种方案是,每个类里自己手写,每有新的类时,都需要手动的去添加这一部分的代码,比较重复繁琐。
- 另一种方案是,让程序自动加上需要统一的代码。说白了就是方法增强,增强后的对象是一个代理对象而非原对象,正是有了bean工厂我们可以对bean进行统一处理,才能更好的实现统一生成代理的策略。
- 结论:Spring的bean容器内放的都是代理对象,而非原对象,我们可以任意定制代理的内容,对原生bean进行功能的扩展。
3、与AOP相关的名词
横切关注点:跨越应用程序多个模块的方法或功能。也就是与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如:日志、安全、缓存、事务 等等。
切面(Aspect):横切关注点被模块化的特殊对象。即,它是一个
类
。通知(Advice):切面必须要完成的工作。即,它是类中的一个
方法
。目标(Target):被通知的对象,也就是原来编写业务逻辑类的对象。
代理(Proxy):向目标对象应用通知后创建的对象。
切入点(PointCut):切面通知执行的“地点”。
连接点(JointPoint):与切入点匹配的执行点。
4、使用Spring实现AOP
使用AOP织入,首先需要导入一个依赖包。
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
(1)使用Spring内置的接口
编写业务接口和实现类
package com.jsx.aop; /** * @InterfaceName: UserService * @Description: TODO 业务接口 * @Author: JiaShiXi * @Date: 2021/2/12 16:59 * @Version: 1.0 **/ public interface UserService { public void register(); //注册业务 public void login(); //登陆业务 }
package com.jsx.aop; /** * @ClassName: UserServiceImpl * @Description: TODO * @Author: JiaShiXi * @Date: 2021/2/12 17:01 * @Version: 1.0 **/ public class UserServiceImpl implements UserService { @Override public void register() { System.out.println("用户注册业务...."); } @Override public void login() { System.out.println("用户登陆业务...."); } }
编写增强类,这里我们写两个,一个前置增强,一个后置增强
package com.jsx.aop; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * @ClassName: LogBefore * @Description: TODO 前置增强类 * @Author: JiaShiXi * @Date: 2021/2/12 17:16 * @Version: 1.0 **/ public class LogBefore implements MethodBeforeAdvice { /** * @param method 目标对象中的方法 * @param args 目标对象中的方法的参数 * @param target 目标对象 * @throws Throwable */ public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName()+"类中的"+method.getName()+"方法被执行了...."); } }
package com.jsx.aop; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; /** * @ClassName: LogAfter * @Description: TODO 后置增强类 * @Author: JiaShiXi * @Date: 2021/2/12 17:22 * @Version: 1.0 **/ public class LogAfter implements AfterReturningAdvice { /** * * @param returnValue 目标对象中执行方法的返回值 * @param method 目标对象中执行的方法 * @param args 目标对象中执行方法的参数 * @param target 目标对象 * @throws Throwable */ public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName()+"类中的"+method.getName()+"方法被执行,返回值是"+returnValue+"..."); } }
在Spring配置文件中注册bean,并配置aop,修改以下导入约束。
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.jsx.aop.UserServiceImpl"></bean> <bean id="logBefore" class="com.jsx.aop.LogBefore"></bean> <bean id="logAfter" class="com.jsx.aop.LogAfter"></bean> <!--配置AOP--> <aop:config> <!-- 切入点配置 expression:切入点表达式,匹配要执行的方法 execution(* com.jsx.aop.UserServiceImpl.*(..)) 第一个*:任意返回值 第二个*:com.jsx.aop.UserServiceImpl类下的所有方法 *(..):所有方法的任意参数 --> <aop:pointcut id="pointcut" expression="execution(* com.jsx.aop.UserServiceImpl.*(..))"/> <!-- 执行环绕配置: advice-ref:增强方法所在的类 pointcut-ref:切入点 --> <aop:advisor advice-ref="logBefore" pointcut-ref="pointcut" /> <aop:advisor advice-ref="logAfter" pointcut-ref="pointcut" /> </aop:config> </beans>
编写测试方式
@Test public void userserviceTest(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("UserContext.xml"); //此处必须为一个接口,不能是实现类,否则会报错 UserService userService = (UserService) applicationContext.getBean("userService"); userService.login(); userService.register(); }
运行结果
com.jsx.aop.UserServiceImpl类中的login方法被执行了.... 用户登陆业务.... com.jsx.aop.UserServiceImpl类中的login方法被执行,返回值是null... com.jsx.aop.UserServiceImpl类中的register方法被执行了.... 用户注册业务.... com.jsx.aop.UserServiceImpl类中的register方法被执行,返回值是null...
(2)自定义类和方法实现
目标业务类不变,依旧是userServiceImpl
写一个我们自定义的切面类
package com.jsx.aop.method2; /** * @ClassName: MyAop * @Description: TODO 自定义的切入类 * @Author: JiaShiXi * @Date: 2021/2/12 20:00 * @Version: 1.0 **/ public class MyAop { public void before(){ System.out.println("【before】执行前置通知...."); } public void after(){ System.out.println("【after】执行后置通知...."); } }
配置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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="com.jsx.aop.method2.UserServiceImpl" /> <bean id="myAop" class="com.jsx.aop.method2.MyAop"/> <!--配置AOP--> <aop:config> <!--配置切面--> <aop:aspect ref="myAop"> <!--配置切入点--> <aop:pointcut id="pointcut" expression="execution(* com.jsx.aop.method2.UserServiceImpl.*(..))"/> <!-- 配置前置通知和后置通知 method:切面类中对应的方法名称 pointcut-ref:切入点引用 --> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
编写测试方法
@Test public void userserviceTest(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("UserContext2.xml"); //此处必须为一个接口,不能是实现类,否则会报错 UserService userService = (UserService) applicationContext.getBean("userService"); userService.login(); userService.register(); }
运行结果
【before】执行前置通知.... 用户登陆业务.... 【after】执行后置通知.... 【before】执行前置通知.... 用户注册业务.... 【after】执行后置通知....
自定义的方式无法拿到对象和方法,不如Spring内置接口灵活。
(3)注解实现(推荐)
创建切面类
package com.jiazhong.spring.aop.aspects; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.context.annotation.EnableAspectJ***; import org.springframework.stereotype.Component; @Component//交给spring进行管理 @Aspect//指定使用该注解的类为一个切面类 @EnableAspectJ***//启用AOP的自动代理(开启Spring AOP模式) public class LogAspect { /*** * 切入点表达式注解,该表达式用于指定要拦截的方法(连接点) * 切入点表达式定义: * 定义一个确定的方法:* com.jiazhong.spring.aop.service.impl.UserServiceImpl.addUser() * 定义一个包中任意类的任意方法而且方法的类型任意:* com.jiazhong.spring.aop.service.impl.*.*(..)) * (..)表示方法的参数可以为任意数量、任意类型 * 定义当前包及其子包:* com.jiazhong.spring.aop.service..*.*(..) * ..表示当前包及其子包 * 排除某个方法:execution(void com.jiazhong.spring.aop.service..*.*(..)) && !execution(void com.jiazhong.spring.aop.service.impl.UserServiceImpl.delUser(..)) */ @Pointcut("execution(* com.jiazhong.spring.aop.service..*.*(..)) && !execution(void com.jiazhong.spring.aop.service.impl.UserServiceImpl.del*(..))") /*** *定义切入点 * 切入点的定义形式和方法的声明类似,但它不是方法而是一个变量 * myPintCut()是切入点的标识(id,变量名),通过标识可以引入该切入点 */ private void myPintCut(){} /*** * 当拦截到切入点定义的连接点(方法)后要做的事情,我们称为通知 * 定义通知: * 1.前置通知:在执行目标方法之前要做的事情 * 2.后置通知:在执行目标方法之后要做的事情 * returning="obj":获得目标方法的返回值并将返回值赋给obj参数,此时在方法内部即可使用 * 3.异常通知:当目标方法出现异常时要做的事情(catch) * 4.最终通知:无论目标方法是否出现异常都要做的事情(finally) * 5.环绕通知:可以在目标方法执行前或执行后或出现异常后或最终要执行的代码,环绕通知的执行时机由开发人员指定而非系统预定 * 环绕通知可以替代上面四个通知 * 环绕通知与上面的四个通知不同,上面的四个通知的执行时机是预定好的,目标方***自动调用,而环绕通知的执行时机 * 是由开发人员决定的,所以在环绕通知中目标方法必须在通知中手动调用 * 环绕通知的参数必须为:ProceedingJoinPoint,其中包含目标方法的调用及方法签名 */ /*** * 前置通知 @Before("myPintCut()")//前置通知注解 public void before(){ System.out.println("执行前置通知...."); } */ /** * 后置通知 * returning="obj":获得目标方法的返回值并将返回值赋给obj参数,此时在方法内部即可使用 */ @AfterReturning(pointcut="myPintCut()",returning = "obj")//后置通知注解 public void afterReturnning(Object obj){ System.out.println("执行后置通知...."); System.out.println(obj); } /*** * 异常通知 @AfterThrowing("myPintCut()")//异常通知注解 public void afterAhrowing(){ System.out.println("执行异常通知...."); } */ /*** * 最终通知 @After("myPintCut()")//最终通知注解 public void after(){ System.out.println("执行最终通知...."); } */ /*** * 环绕通知 * 环绕通知的参数必须为ProceedingJoinPoint * ProceedingJoinPoint参数中包含了目标方法及其他封装的操作 @Around("myPintCut()")//环绕通知注解 public void around(ProceedingJoinPoint proceedingJoinPoint) { try { System.out.println("环绕通知---->前置执行。。。。"); Object obj = proceedingJoinPoint.proceed();//调用目标方法 System.out.println(obj); //获得方法的签名(方法声明) Signature signature = proceedingJoinPoint.getSignature(); //System.out.println(signature); String methodName = signature.getName(); System.out.println(methodName); System.out.println("环绕通知---->后置执行。。。。"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知---->异常执行。。。。"); }finally{ System.out.println("环绕通知---->最终执行。。。。"); } } */ }
六、Spring与MyBatis整合
1、导入相关jar包
<!-- spring-context Spring上下文包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.12.RELEASE</version> </dependency> <!-- spring-tx Spring事务管理包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.12.RELEASE</version> </dependency> <!-- spring-jdbc Spring对JDBC的支持--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.12.RELEASE</version> </dependency> <!-- mybatis-spring mybatis与Spring整合的包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!-- aspectjweaver织入包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> <scope>runtime</scope> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> <!-- log4j-core --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.0</version> </dependency> <!--junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency>
2、配置log4j2日志
在resources资源文件夹下,新建"log4j2.xml"配置文件(文件名唯一),编写log4j2的相关配置。
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Logger name="com.jsx.dao" level="debug" additivity="false"> <AppenderRef ref="Console"/> </Logger> <Root level="trace"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
3、编写druid的相关配置
在resources资源文件夹下,新建"db.properties"配置文件,编写druid的相关配置。
#mysql数据库的驱动类 druid.driverClassName=com.mysql.jdbc.Driver #mysql的url druid.url=jdbc:mysql://localhost:3306/db_bilibili?userSSL=false #mysql数据库的账号 druid.userName=root #mysql数据库的密码 druid.password=root
4、编写实体类
package com.jsx.model; /** * @ClassName: BiliBili * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/13 16:31 * @Version: 1.0 **/ public class BiliBili { private Integer id; private String title; private String cover; private String orders; private String index_show; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getCover() { return cover; } public void setCover(String cover) { this.cover = cover; } public String getOrders() { return orders; } public void setOrders(String orders) { this.orders = orders; } public String getIndex_show() { return index_show; } public void setIndex_show(String index_show) { this.index_show = index_show; } @Override public String toString() { return "BiliBili{" + "id=" + id + ", title='" + title + '\'' + ", cover='" + cover + '\'' + ", orders='" + orders + '\'' + ", index_show='" + index_show + '\'' + '}'; } }
5、编写service层相关代码
package com.jsx.service; import com.jsx.model.BiliBili; import org.springframework.stereotype.Service; /** * @InterfaceName: BiliBiliService * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/13 16:36 * @Version: 1.0 **/ public interface BiliBiliService { /** * 添加数据 * @param biliBili */ public void addBiliBili(BiliBili biliBili); }
package com.jsx.service.impl; import com.jsx.dao.BiliBIliDao; import com.jsx.model.BiliBili; import com.jsx.service.BiliBiliService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @ClassName: BiliBiliServiceImpl * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/13 16:37 * @Version: 1.0 **/ @Service("biliBiliService") public class BiliBiliServiceImpl implements BiliBiliService { @Autowired private BiliBIliDao biliBIliDao; public void addBiliBili(BiliBili biliBili) { biliBIliDao.addBiliBili(biliBili); System.out.println("添加成功!"); } }
6、编写Dao层相关代码
package com.jsx.dao; import com.jsx.model.BiliBili; import org.apache.ibatis.annotations.Insert; /** * @InterfaceName: BiliBIliDao * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/13 16:32 * @Version: 1.0 **/ @Repository public interface BiliBIliDao { /** * 添加数据 * @param biliBili */ @Insert("insert into bilibili values(default,#{title},#{cover},#{orders},#{index_show})") public void addBiliBili(BiliBili biliBili); }
7、编写配置类
package com.jsx.config; import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; /** * @ClassName: Appconfig * @Description: TODO * @Author: JiaShiXi * @Date: 2021/1/13 15:53 * @Version: 1.0 **/ @MapperScan("com.jsx.dao") //Mapper映射扫描器 @ComponentScan("com.jsx.service") //组件扫描器 @Configuration //配置类 @PropertySource("classpath:/db.properties") //资源文件,【classpath:】表示类路径下 public class AppConfig { @Value("${druid.driverClassName}") //资源文件取值方法 private String driverClassName; @Value("${druid.url}") private String url; @Value("${druid.userName}") private String userName; @Value("${druid.password}") private String password; /** * 配置Druid数据源 * @return */ @Bean //将当前数据源对象交给Spring进行管理 public DataSource dataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(userName); dataSource.setPassword(password); return dataSource; } /** * 配置sqlSessionFactory * @param dataSource 数据源,自动映射spring中的dataSource对象,保证其只有一个 * @return */ @Autowired //参数自动映射(映射Spring中托管的数据源对象) @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean.getObject(); } /** * 配置事务管理器 * @param dataSource * @return */ @Autowired @Bean public DataSourceTransactionManager txManager(DataSource dataSource){ DataSourceTransactionManager txManager = new DataSourceTransactionManager(); txManager.setDataSource(dataSource); return txManager; } }
8、编写测试方法
@Test public void test1(){ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); BiliBiliService biliBiliService = (BiliBiliService) context.getBean("biliBiliService"); BiliBili biliBili = new BiliBili(); biliBili.setTitle("标题"); biliBili.setCover("www.baidu.com"); biliBili.setIndex_show("sss"); biliBili.setOrders("555万人"); biliBiliService.addBiliBili(biliBili); }
9、运行结果
11:05:31.832 [main] DEBUG com.jsx.dao.BiliBIliDao.addBiliBili - ==> Preparing: insert into bilibili values(default,?,?,?,?) 11:05:31.857 [main] DEBUG com.jsx.dao.BiliBIliDao.addBiliBili - ==> Parameters: 标题(String), www.baidu.com(String), 555万人(String), sss(String) 11:05:31.886 [main] DEBUG com.jsx.dao.BiliBIliDao.addBiliBili - <== Updates: 1 11:05:31.887 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4426bff1] 添加成功!
七、声明式事务
1、事务特性
- 原子性(Atomicity):事务是数据库逻辑工作单元,事务中包含的操作要么都执行成功,要么都执行失败。
- 一致性(Consistency):事务执行的结果必须是使数据库数据从一个一致性状态变到另外一种一致性状态。当事务执行成功后就说数据库处于一致性状态。如果在执行过程中发生错误,这些未完成事务对数据库所做的修改有一部分已经写入物理数据库,这时数据库就处于不一致状态。
- 隔离性(Isolation):一个事务的执行过程中不能影响到其他事务的执行,即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行,各个事务之间互不干扰。
- 持续性(Durability):即一个事务一旦提交,它对数据库的改变是永久性的。之后的其他操作不应该对其执行结果有任何影响。
2、Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式和声明式的事务管理。
编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:必须在每个事务操作的逻辑中包含额外的事务管理代码
声明式事务管理
- 一般情况下比编程式事务好用。
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
- 将事务管理作为横切关注点,通过AOP方法将其织入。Spring中通过Spring AOP框架支持声明式事务管理。
事务管理器
- 无论使用Spring的哪种事务管理策略(编程式或声明式)事务管理器都是必须的。
- Spring的核心事务管理,封装了一种独立于技术的方法。
头文件的导入
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
声明式事务
声明式事务其实就是AOP的典型应用,都是在业务方法中将事务的开启关闭织入。
<!--将声明式事务对象交给Spring容器进行管理--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--引用数据源配置--> <property name="dataSource" ref="dataSource" /> </bean> <!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--配置哪些方法使用什么样的事务name,配置事务的传播特性propagation,read-only="true"只读,不能增删改,一般用于查找的方法上--> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="get*" propagation="SUPPORTS" read-only="true"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> <!--因为大多数有关于数据库操作的方法都需要事务的支持,所以在实际使用中配置以下的一个标签即可--> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--配置aop织入事务--> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.xinzhi.service.impl.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>
3、Spring事务传播特性(***)
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法之间传播。
Spring支持7种事务传播行为:
PROPAGATION_REQUIRED ,默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。
PROPAGATION_SUPPORTS,从字面意思就知道,supports,支持,该传播级别的特点是,如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些
并非原子性的非核心业务逻辑操作
。应用场景较少。PROPAGATION_MANDATORY , 该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。
PROPAGATION_REQUIRES_NEW ,从字面即可知道,new,每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。
- PROPAGATION_NOT_SUPPORTED ,这个也可以从字面得知,not supported ,不支持,当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板抱起来就可以了。
PROPAGATION_NEVER,该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!这个级别上辈子跟事务有仇。
PROPAGATION_NESTED,字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。