Spring 5| 轻量级的开源JavaEE框架
一、Spring框架的概述
1、Spring是轻量级的开源的JavaEE框架
2、Spring可以解决企业应用开发的复杂性
3、Spring有两个核心的部分:IOC(控制反转)和AOP(面向切面编程)
4、Spring特点
(1)方便解耦,简化开发
(2)Aop编程支持
(3)方便程序的测试
(4)方便集成各种优秀框架
(5)方便进行事务的操作
(6)降低API的开发难度
二、Spring配置小案例
1.配置Spring的配置文件以及加载
//1.加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("myBean.xml");
//2.获取配置文件创建的对象
User user = context.getBean("user1", User.class);
System.out.println(user);
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="com.cuit.bean.User">
<property name="name" value="张三"></property>
<property name="sex" value="男"></property>
</bean>
</beans>
三、IOC
1、IOC(概念和原理)
- IOC容器底层就是对象工厂
- 控制反转,即将对象的创建和对象之间的调用过程交给Spring容器来管理
- 使用IOC的目的是为了解耦,降低耦合度
- IOC的底层是xml解析、工厂模式、反射
2、IOC底层有两种接口
BeanFactory:加载配置文件的时候不创建对象,调用对象时才创建对象
ApplicationContext:加载配置文件的时候创建好对象,可以为Bean配置lazy-init=true来让Bean延迟实例化
3、IOC操作Bean管理
Spring创建对象、Spring注入属性
Spring中有两种类型的bean
普通bean:配置文件中定义bean类型就是返回类型
工程bean:在配置文件定义bean类型可以和返回类型不一样
3.1、基于xml方式创建对象
创建对象时,默认使用无参构造方式创建对象
id:对于创建对象的唯一标识
class:类的全路径(包类路径),即返回类型
<bean id="user1" class="com.cuit.bean.User">
3.2、基于xml方式设置属性(DI依赖注入)
DI:依赖注入,就是注入属性
3.2.1、构造器方式注入
在创建的类中,添加上有参构造器
public class Book {
private String bname;
private String bautohr;
public Book(String bname, String bautohr) {
this.bname = bname;
this.bautohr = bautohr;
}
public Book() {
}
}
xml文件
constructor-arg标签用于设置构造器参数属性值
<bean id="book" class="com.cuit.bean.Book" >
<constructor-arg name="bautohr" value="达摩祖师"></constructor-arg>
<constructor-arg name="bname" value="易筋经"></constructor-arg>
</bean>
3.2.2、set方式进行注入
在创建的类中设置属性,并且带有set方法
package com.cuit.bean;
public class User {
private String name;
private int age;
private String sex;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
}
xml文件
<bean id="user" class="com.cuit.bean.User">
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
<property name="sex" value="男"></property>
</bean>
设置一些特殊值空值
<bean id="user" class="com.cuit.bean.User">
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
<!--设置空值-->
<property name="sex">
<null></null>
</property>
<property name="sex">
<!--设置一些特殊值,比如<<南京>> idea中输入cd后有提示-->
<value> <![CDATA[
<<南京>>
]]></value>
</property>
</bean>
注入其他bean的方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:p="//www.springframework.org/schema/p"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.cuit.bean.User">
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
<!--外部bean注入属性-->
<property name="book" ref="book"></property>
<property name="sex" value="男"></property>
</bean>
<bean id="book" class="com.cuit.bean.Book">
<constructor-arg name="bautohr" value="达摩祖师"></constructor-arg>
<constructor-arg name="bname" value="易筋经"></constructor-arg>
</bean>
<!--直接使用p命名空间注入属性-->
<bean id="user1" class="com.cuit.bean.User" p:name="李四" p:age="21" p:sex="男">
</bean>
<bean name="emp" class="com.cuit.bean.Emp">
<property name="ename" value="王五"/>
<property name="gender" value="女"/>
<!--内部bean-->
<property name="dept">
<bean id="dept" class="com.cuit.bean.Dept">
<property name="dname" value="研发部"/>
</bean>
</property>
</bean>
<bean name="emp1" class="com.cuit.bean.Emp">
<property name="ename" value="王五"/>
<property name="gender" value="女"/>
<!--级联赋值-->
<property name="dept" ref="dept1"></property>
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept1" class="com.cuit.bean.Dept">
<property name="dname" value="研发部"/>
</bean>
</beans>
注入集合类型属性
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:p="//www.springframework.org/schema/p"
xmlns:util="//www.springframework.org/schema/util"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd
//www.springframework.org/schema/util //www.springframework.org/schema/beans/spring-util.xsd">
<util:list id="list">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<bean id="stu" class="com.cuit.bean.Stu">
<property name="course">
<!--数组的注入方式-->>
<array>
<value>数学</value>
<value>英语</value>
<value>语文</value>
</array>
</property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
<!--可以使用<ref bean="容器中的bean的id或者name,但对象必须与list申请时对象相对应的"-->
</list>
</property>
<property name="maps">
<map>
<entry key="key1" value="val1"></entry>
<entry key="key2" value="val2"></entry>
<entry key="key2" value="val3"></entry>
<!--可以使用<ref bean="容器中的bean的id或者name,但对象必须与map申请时对象相对应的"-->
</map>
<!--
<props>
<prop key="key1">value1</prop>
</props>
-->
</property>
<property name="sets">
<set>
<value>cf</value>
<value>dnf</value>
<value>lol</value>
<!--可以使用<ref bean="容器中的bean的id或者name,但对象必须与set申请时对象相对应的"-->
</set>
</property>
</bean>
</beans>
set:用户属性set的情况,无重复,有个ref标签,通过引入bean的id,引入对象
list:类似于set但是值时可以重复的,也有个ref标签,通过引入bean的id,引入对象
map:类死props,键和值不一定要用String,专用于map,有个value-ref,对应于bean的id,引入对象
的
3.2.3、其他注入方式
需要引入第三方依赖
(1)、p名称空间注入,可以简化基于xml方式注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:p="//www.springframework.org/schema/p"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
<!--直接使用p命名空间注入属性-->
<bean id="user" class="com.cuit.bean.User" p:name="李四" p:age="21" p:sex="男">
</bean>
</beans>
(2)、
3.2.4、bean的作用域
在Spring里面,可以设置创建bean实例是单实例还是多实例,默认单实例对象
通过scope标签缺省的情况默认为singleton为单实例,prototype为多实例
singleton单实例模式
无论调用多少次getBean()方法,只会在堆空间中生成一个对象
public void testStu(){
ApplicationContext context = new ClassPathXmlApplicationContext("myBean2.xml");
//User user = (User)context.getBean("user");
/*知道输出类型之后就不需要每一次get之后都去强转类型了*/
Stu stu1 = context.getBean("stu", Stu.class);
Stu stu2 = context.getBean("stu", Stu.class);
System.out.println(stu1);
System.out.println(stu2);
System.out.println(stu1 == stu2);
}
<util:list id="list">
<value type="java.lang.String">易筋经</value>
<value type="java.lang.String">九阴真经</value>
<value type="java.lang.String">九阳神功</value>
</util:list>
<bean id="stu" class="com.cuit.bean.Stu" scope="singleton">
<property name="lists" ref="list"/>
</bean>
输出
com.cuit.bean.Stu@38467116
com.cuit.bean.Stu@38467116
true
单实例模式特点
1、把bean放在IOC容器中统一进行管理,只在初始化加载的时候实例化一次,一方面提高了效率,另一方面大大降低了内存开销(针对创建对象的次数)
2、不同的请求可以同时拿这个实例来用,执行实例中不同的方法时,可以同时进行,但是不可以同时执行该实例的同一个方法。其实就是单例模式的线程安全问题,
3、实例里面不可以有成员变量,因为成员变量共享的话,一个对它修改,另一个再拿来用,用的是修改之后的,会不安全。
4、只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。
prototype(多实例模式)
<bean id="stu" class="com.cuit.bean.Stu" scope="prototype">
<property name="lists" ref="list"/>
</bean>
输出
com.cuit.bean.Stu@5b7a7f33
com.cuit.bean.Stu@790da477
false
request、ssession、application这些只会在web开发中使用
request作用域
对应一个http请求和生命周期,当http请求调用作用域为request的bean的时候,Spring便会创建一个新的bean,在请求处理完成之后便及时销毁这个bean。
session作用域
Session中所有http请求共享同一个请求的bean实例。Session结束后就销毁bean。
globalSession作用域
与session大体相同,但仅在portlet应用中使用。
3.2.5、bean生命周期
(一)、不加后置处理器
一共五步
第一步、通过无参构造器创建bean的实例对象
第二步、对bean的set()方法进行属性赋值以及对其他bean引用
第三步、调用bean的初始化方法(通过init-method标签配置)
第四步、bean可以使用了
第五步、当容器关闭时,调用bean的销毁方法(通过destroy-method标签配置)
(二)、加上后置处理器
需要创建一个类实现BeanPostProcessor接口,并且需要在xml文件中添加一个实现BeanPostProcessor接口的类的bean,相当于给所有的bean添加上了后置处理器
一共七步
第一步、通过无参构造器创建bean的实例对象
第二步、对bean的set()方法进行属性赋值以及对其他bean引用
第三步、调用后置处理器中的postProcessBeforeInitialization
方法
第四步、调用bean的初始化方法(通过init-method标签配置)
第五步、调用后置处理器中的postProcessAfterInitialization
方法
第六步、bean可以使用了
第七步、当容器关闭时,调用bean的销毁方法(通过destroy-method标签配置)
public class MyBeanPost 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;
}
}
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
3.2.6、bean的自动装配
根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
bean标签autowire属性设置自动装配,有两个常用的属性值
byName:根据属性名称注入,要求注入值bean的id和属性名称一致
byType:根据属性类型注入
<bean id="emp" class="com.cuit.autowire.Emp" autowire="byName"></bean>
<!--<bean id="emp" class="com.cuit.autowire.Emp" autowire="byName"></bean>-->
<bean id="dept" class="com.cuit.autowire.Dept"></bean>
3.3、基于注解的方式创建对象
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值..) (2)使用注解,注解作用在类上面,方法上面,属性上面 (3)使用注解目的:简化 xml 配置
Spring针对bean管理创建对象提供注解
(1)@Component
(2)@Service 一般用于业务逻辑层
(3)@Controller 一般用于web层
(4)@Repository 一般用于dao层
上面四个注解功能是一样的,都可以用来创建 bean 实例
第一步、引入Aop的依赖包
第二步、使用注解要开启组件扫描,前提引入context名称空间
<!--指定要扫描的包,包下面的注解就会生效-->
<context:component-scan base-package="com.cuit.testdemo"/>
第三步、在类上方使用上面任一注解都可以创建对应的bean
@Service("userService")
public class UserService {
public void add() {
System.out.println("Service add.........");
}
}
注解创建对象小结:首先加载,当读取到xml文件中开启组件扫描,去对应的包中寻找带有注解的类,并且创建对应的bean实例
3.4、基于注解的方式属性注入
@Autowired 根据属性类型进行自动装配
@Qualifier 根据属性的名称进行注入,配合@Autowired一起使用
@Resource 可以根据名称和类型进行注入 此注解是jdk自带的,位于javax.annotation.Resource,jdk11已经被移除了
@Value 针对普通数据类型
第一步 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
第二步 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解
@@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("userDao add ..........");
}
}
@Service
public class UserService {
@Autowired
@Qualifier(value = "userDaoImpl")
private UserDao userDao;
public void add() {
System.out.println("Service add.........");
userDao.add();
}
}
3.5、完全注解开发
(1)创建一个配置类,代替xml配置文件
@Configuration //作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.cuit"})
public class SpringConfig {
}
(2) 编写测试类
和xml配置文件不同的是在加载配置文件时,用的是AnnotationConfigApplicationContext
@Test
public void test2(){
//基于xml配置文件的方式加载配置文件
//ApplicationContext context = new ClassPathXmlApplicationContext("myBean2.xml");
//基于配置类的方式加载配置文件
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
四、AOP
1、AOP(概念)
(1)面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
比如一个登录流程,需要在登录功能基础上添加一个权限判断,可以利用AOP单独开一个权限判断,利用其他方式对原来的登录流程进行增强
2、动态代理
动态代理有两种
第一种有接口的情况,使用JDK动态代理
第二种没有接口的情况,使用CGLIB动态代理
JDK动态代理
JDK动态代理基于有接口的情况
interface Car{
void sell();
}
创建一个被代理类
class Audi implements Car{
@Override
public void sell() {
System.out.println("奥迪卖车出去");
}
}
创建一个代理类
class CarFactory{
public static Object getNewInstance(Object audi){
CarInvocationHandler handler = new CarInvocationHandler(new Audi());
//利用Proxy中的newProxyInstance方法生成一个被代理对象
//因为newProxyInstance方法中第三个参数类型为InvocationHandler,所以创建一个新的类实现InvocationHandler接口
//此方法需要传三个参数,参数一:实现InvocationHandler接口类的类加载器;参数二:被代理类实现的一些接口;参数三:为此方法的回调接口,用于调用InvocationHandler实例中的invoke方法
return Proxy.newProxyInstance(audi.getClass().getClassLoader(), audi.getClass().getInterfaces(), handler);
}
}
class CarInvocationHandler implements InvocationHandler{
//被代理对象引用,invoke方法里面method需要使用这个被代理对象
private Object car;
public CarInvocationHandler(Object car){
this.car = car;
}
//InvocationHandler接口中有一个invoke方法,当一个代理实例的方法被调用时,代理方法将被编码并分发到 InvocationHandler接口的invoke方法执行。
/**
* invoke() 方法有下面三个参数:
* proxy :动态生成的代理类
* method : 与代理类对象调用的方法相对应
* args : 当前 method 方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("客户看车");
//调用被代理对象的真实方法
Object invoke = method.invoke(car, args);
System.out.println("客户走人");
return invoke;
}
}
测试类
public class JDKProxyTest {
public static void main(String[] args) {
Audi audi = new Audi();
Car newInstance = (Car) CarFactory.getNewInstance(audi);
//这里执行sell方法,实际执行的是invoke方法
newInstance.sell();
}
}
输出
客户看车
奥迪卖车出去
客户走人
JDK动态代理使用步骤:
- 定义一个接口及其实现类;
- 自定义
InvocationHandler
并重写invoke()
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象;
invoke()
方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke()
方法,然后 invoke()
方法代替我们去调用了被代理对象的原生方法。
Java动态代理优缺点:
优点:
1.Java本身支持,不用担心依赖问题,随着版本稳定升级;
2.代码实现简单;
缺点:
1.目标类必须实现某个接口,换言之,没有实现接口的类是不能生成代理对象的;
2.代理的方法必须都声明在接口中,否则,无法代理;
3.执行速度性能相对cglib较低;
CGLIB动态代理
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。
引入CGLIB的依赖包
<!-- cglib 动态代理依赖 begin -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version></dependency>
<!-- cglib 动态代理依赖 stop -->
创建目标类
class Dog{
public void call(){
System.out.println("狗---汪汪");
}
final public void run() {
System.out.println("狗----run");
}
}
代理类需要实现MethodInterceptor接口,方法调用会被转发到该类的intercept()方法。
intercept方法中有四个参数
- o 代表Cglib 生成的动态代理类 对象本身
- method 代理类中被拦截的接口方法 Method 实例
- objects 接口方法参数
- methodProxy 用于调用父类真正的业务类方法。可以直接调用被代理类接口方法
public class CGLIBProxy implements MethodInterceptor {
//用于生成 Cglib 动态代理类工具方法
//zclass 代表需要 被代理的 委托类的 Class 对象
public Object getInstance(Class zclass){
//创建加强器,用来创建动态代理类
Enhancer enhancer = new Enhancer();
//为代理类指定需要代理的类,也即是父类
enhancer.setSuperclass(zclass);
//设置方法拦截器回调引用,对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截
enhancer.setCallback(new CGLIBProxy());
// 获取动态代理类对象并返回
return enhancer.create();
}
/**
* o 代表Cglib 生成的动态代理类对象本身
* method 代理类中被拦截的接口方法Method 实例
* objects 接口方法参数
* methodProxy 用于调用父类真正的业务类方法。可以直接调用被代理类接口方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用一些增强的方法");
Object invokeSuper = methodProxy.invokeSuper(o, objects);
//方法实质都是调用父类方法
//Object invoke = method.invoke(o, objects);
System.out.println("方法运行完成");
return invokeSuper;
}
}
测试类
public class CGLIBProxyTest{
public static void main(String[] args) {
CGLIBProxy cGLIBProxy = new CGLIBProxy();
Dog instance = (Dog) cGLIBProxy .getInstance();
instance.call();
instance.run();
}
}
输出
调用一些增强的方法
狗---汪汪
方法运行完成
狗----run
CGLIB动态代理使用步骤
- 定义一个类;
- 自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; - 通过
Enhancer
类的create()
创建代理类;
CGLIB优缺点:
优点:
1.代理的类无需实现接口;
2.执行速度相对JDK动态代理较高;
缺点:
1.字节码库需要进行更新以保证在新版java上能运行;
2.动态创建代理对象的代价相对JDK动态代理较高;
3.代理的对象不能是final关键字修饰的
参考博客
//cloud.tencent.com/developer/article/1461796
//nilzzzz.github.io/2020/08/JDK+-CGLIB动态代理实战/
3、AOP术语
连接点:可以被增强的方法
切入点:实际被增强的方法
通知(增强):实际增强的逻辑部分
通知的类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
切面:把通知应用到切入点的过程
4、Spring中AOP操作
Spring框架一般都是基于AspectJ实现AOP操作
AspectJ不是Spring的组成部分,独立AOP框架,一般把AspectJ和Spring一起使用,进行AOP操作
基于 AspectJ 实现 AOP 操作有两种方式 (1)基于 xml 配置文件实现 (2)基于注解方式实现(使用)
基于注解方式实现AOP操作
创建一个配置类
@Configuration //作为配置类,替代xml配置文件
@EnableAspectJAutoProxy //注册AspectJ 的自动代理默认为false启用JDK动态代理,为true强制使用CGLIB动态代理
@ComponentScan(basePackages = {"com.cuit.aop"})
public class SpringConfig {
}
创建一个被代理类User
@Component("user")
public class User {
public void add() {
System.out.println("add........");
}
}
创建一个代理类,并且在类上添加@Aspect注解,将当前类指定为一个切面
@Component
@Aspect
public class UserProxy {
//具有相同切入点时,在每一个通知里面的注解里面写切入点太麻烦,可以用@Pointcut注解统一设置,然后通知注解里面可以传带有@Pointcut的方法名
//切入点设置
@Pointcut("execution(* com.cuit.aop.User.add(..))")
public void pointcut(){
}
//@Before前置通知,目标方法前执行
@Before("pointcut")
public void berfore(){
System.out.println("berfore前置输出");
}
//@AfterReturning后置通知,目标方法执行完执行
@AfterReturning("execution(* com.cuit.aop.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning后置输出");
}
//@AfterThrowing异常通知,目标方法抛出错误时执行
@AfterThrowing("execution(* com.cuit.aop.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing抛出错误时输出");
}
//@Around环绕通知,目标方法前后都会执行
@Around("execution(* com.cuit.aop.User.add(..))")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("around环绕前输出");
point.proceed();
System.out.println("around环绕后输出");
}
//@After最终通知,目标方法执行完执行
@After("execution(* com.cuit.aop.User.add(..))")
public void after(){
System.out.println("after最终输出");
}
}
测试类
@Test
public void test3(){
//ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("user", User.class);
user.add();
}
运行结果
around环绕前输出
berfore前置输出
add........
afterReturning后置输出
after最终输出
around环绕后输出
基于xml方式实现AOP操作
<!--添加扫描-->
<context:component-scan base-package="com.cuit.aop"/>
<!--创建对象-->
<bean id="book" class="com.cuit.aop.User"></bean>
<bean id="bookProxy" class="com.cuit.aop.UserProxy"></bean>
<!--开启AspectJ自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--配置aop增强-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="p" expression="execution(* com.cuit.aop.User.add(..))"/>
<!--配置切面-->
<aop:aspect>
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"></aop:before>
</aop:aspect>
</aop:config>
多个代理类设置优先级
在代理类上加上注解@Order(数值类型)数值从0开始,数值越小,优先级越高
五、Spring事务管理
事务操作(事务概念)
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
典型场景:银行转账
事务四个特性(ACID)
(1)原子性 (Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。
(2)一致性 (Consistency)
官网上事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
换一种方式理解就是:事务按照预期生效,数据的状态是预期的状态。
(3)隔离性 (Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
(4)持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
在 Spring 进行事务管理操作有两种方式:编程式事务管理和声明式事务管理(使用)
注解声明事务管理
操作步骤如下
1、在spring配置文件中添加事务管理器,也就是下面的transactionManager
2、引入tx名称空间,开启事务注解
<context:component-scan base-package="com.cuit"></context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/db" />
<property name="username" value="username" />
<property name="password" value="password" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property><!--set方式注入-->
</bean>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在 service 类上面(或者 service 类里面方法上面)添加事务注解@Transactional
如果把这个注解添加类上面,这个类里面所有的方法都添加事务
如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private JdbcTemplate jdbcTemplate;
public void change(){
userDao.update(1,100);
//int i = 10/0;
userDao.update(2,-100);
}
}
@Repository()
public class UserDaoImpl implements UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public void update(int id, int value) {
String sql = "update test set money = money + ? where id = ?";
int update = jdbcTemplate.update(sql, value, id);
}
}
1、在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数
2、propagation:事务传播行为
(1)多事务方法直接进行调用,这个过程中事务是如何进行管理的
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法时事务如何传播。
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
具体情况可以参考下面链接
Spring事务传播行为详解 – SegmentFault 思否
3、ioslation:事务隔离级别
(1)事务有隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
(2)有三个读问题:脏读、不可重复读、虚(幻)读
脏读:事务A修改了表中的一行数据,但没有提交,这时候事务B读取了被事务A修改后的数据,之后事务A因为某种原因回滚,那么事务B读取的数据就是脏的
不可重复读:事务A先读取了事务B修改前的数据,然后事务B提交了,这时事务A又去读了一次数据,结果读到的是事务B修改后的数据,产生了一次事务中读取数据结果不一致的现象,为不可重复读
幻读:事务A先读取了事务B添加前的数据,然后事务B提交了,这时事务A又去读了一次数据,结果读到的是事务B添加后的数据,产生了一次事务中读取数据条数结果不一致的现象,为幻读
(3)通过设置事务隔离级别,解决读问题
不同隔离级别存在不同的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
4、timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
5、readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
6、rollbackFor:回滚
(1)设置出现哪些异常进行事务回滚
7、noRollbackFor:不回滚
(1)设置出现哪些异常不进行事务回滚
xml声明事务管理
1、在 spring 配置文件中进行配置
第一步 配置事务管理器
第二步 配置通知
第三步 配置切入点和切面,需要引入AOP名称空间
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/book" />
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property><!--set方式注入-->
</bean>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置通知-->
<tx:advice id="interceptor">
<tx:attributes>
<tx:method name="select" isolation="DEFAULT"></tx:method>
<tx:method name="update" isolation="READ_COMMITTED"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置切入点和切面-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.cuit.service.UserService.*(..))"/>
<aop:advisor advice-ref="interceptor" pointcut-ref="pc"></aop:advisor>
</aop:config>
完全注解声明事务管理
创建配置类,使用配置类替代 xml 配置文件
@Configuration
@ComponentScan("com.cuit") //组件扫描
@EnableTransactionManagement //开启事务
public class StartConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getTransactionManager(DruidDataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
六、Spring5新特性
1、整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除
2、Spring 5.0 框架自带了通用的日志封装
(1)Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
(2)Spring5 框架整合 Log4j2
第一步 引入 jar 包
第二步 创建 log4j2.xml 配置文件,这个文件名称是固定的
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE >
ALL -->
<!--Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置,
当设置成 trace 时,可以看到 log4j2 内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的 appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-
5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义 logger,只有定义 logger 并引入的 appender,appender 才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root 作为
默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
3、Spring5 框架核心容器支持@Nullable 注解
实质是告诉编译器,接受空值,并且如果重写该方法,则还应接受空值。,
1)@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以
为空,参数值可以为空
2)注解用在方法上面,方法返回值可以为空
3)注解使用在方法参数里面,方法参数可以为空
4)注解使用在属性上面,属性值可以为空
4、Spring5 核心容器支持函数式风格 GenericApplicationContext
5、Spring5 支持整合 JUnit5
第一步 引入 JUnit5 的 jar 包
第二步 创建测试类,使用注解完成
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
//也可以使用复合注解代替上面两个
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}