二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
为什么使用代理模式:可以增强功能,可以保护代理目标,可以让两个不能直接交互的目标进行交互。
初始:
package com.songzhishu.proxy.service;
/**
* @BelongsProject: Spring6
* @BelongsPackage: com.songzhishu.proxy.service
* @Author: 斗痘侠
* @CreateTime: 2023-10-17 11:49
* @Description: TODO
* @Version: 1.0
*/
public class OrderServiceImpl implements OrderService{
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成");
}
@Override
public void detail() {
try {
Thread.sleep(2541);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单信息如下:******");
}
@Override
public void modify() {
try {
Thread.sleep(1010);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
}
现在想统计每一个模块的耗时,怎么办,直接在原来的代码上修改!
方法一:硬编码
这样可以是可以,但是每一个模块都要写重复的代码,而且违背OCP的开闭原则!
方法二:继承重写方法
创建一个子类,然后继承实现类后重写方法也可以实现功能的拓展。
这种解决了问题,没有违背OCP原则,但是使用继承增强了耦合度
方法三:静态代
package com.songzhishu.proxy.service;
/**
* @BelongsProject: Spring6
* @BelongsPackage: com.songzhishu.proxy.service
* @Author: 斗痘侠
* @CreateTime: 2023-10-17 12:50
* @Description: 代理对象
* @Version: 1.0
*/
public class OrderServiceProxy implements OrderService {
//要包含公共的功能 达到和目标对象一样的功能 要执行目标对象中目标方法
//将目标对象作为代理对象的一个属性
private OrderService target; //使用这种方式要比继承的耦合度低 注入公共接口要比实现类好
public OrderServiceProxy(OrderService target) {
//通过构造方法赋值
this.target = target;
}
@Override
public void generate() {
//使用代理方法添加增强功能
long begin = System.currentTimeMillis();
target.generate();
//调用目标对象目标功能
long end = System.currentTimeMillis();
System.out.println("耗费时长"+(end - begin)+"毫秒");
}
@Override
public void modify() {
//使用代理方法添加增强功能
long begin = System.currentTimeMillis();
//调用目标对象目标功能
target.modify();
long end = System.currentTimeMillis();
System.out.println("耗费时长"+(end - begin)+"毫秒");
}
@Override
public void detail() {
//使用代理方法添加增强功能
long begin = System.currentTimeMillis();
//调用目标对象目标功能
target.detail();
long end = System.currentTimeMillis();
System.out.println("耗费时长"+(end - begin)+"毫秒");
}
}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
公共接口:
package com.songzhishu.spring6.service;
/**
* @BelongsProject: Spring6
* @BelongsPackage: com.songzhishu.proxy.service
* @Author: 斗痘侠
* @CreateTime: 2023-10-17 11:45
* @Description: 公共接口
* @Version: 1.0
*/
public interface OrderService {
//生成订单
void generate();
//修改订单
void modify();
//查看订单
void detail();
//获得名字
String getName();
}
实现类:
package com.songzhishu.spring6.service;
/**
* @BelongsProject: Spring6
* @BelongsPackage: com.songzhishu.proxy.service
* @Author: 斗痘侠
* @CreateTime: 2023-10-17 11:49
* @Description: TODO
* @Version: 1.0
*/
public class OrderServiceImpl implements OrderService{
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成");
}
@Override
public void detail() {
try {
Thread.sleep(2541);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单信息如下:******");
}
@Override
public void modify() {
try {
Thread.sleep(1010);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
@Override
public String getName() {
System.out.println("getName方法执行");
return "张胜男";
}
}
客户端:
package com.songzhishu.spring6.client;
import com.songzhishu.spring6.service.OrderService;
import com.songzhishu.spring6.service.OrderServiceImpl;
import com.songzhishu.spring6.service.TimeInvocationHandler;
import com.songzhishu.spring6.utils.ProxyUtil;
import java.lang.reflect.Proxy;
/**
* @BelongsProject: Spring6
* @BelongsPackage: com.songzhishu.spring6.client
* @Author: 斗痘侠
* @CreateTime: 2023-10-17 15:45
* @Description: TODO
* @Version: 1.0
*/
public class ClientTest {
public static void main(String[] args) {
//创建目标对象
OrderService target =new OrderServiceImpl();
//创建代理对象
/*
* Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器);
* 第一步 在内存中创建一个代理类的字节码文件
* 第二步 通过代理类来实例化代理类对象
*
* 参数一 ClassLoader loader
* 类加载器:内存中和硬盘上的class其实没有太大区别,都是class文件,都要加载到java的虚拟机中才能运行
* 注意:目标类的类加载器和代理类的加载器要使用的是同一个
*
* 参数二 Class>[] interfaces
* 代理类和目标类要实现同一个或者同一些接口
*
* 参数三 InvocationHandler h 调用处理器类 实现一个接口
* 然后可以编写增强代码
* */
//使用啦util工具
OrderService proxy = (OrderService) ProxyUtil.newProxyInstance(target);
//使用代理对象调用代理方法, 如果增强的话,目标方法需要执行
proxy.generate();
proxy.detail();
proxy.modify();
String name = proxy.getName();
System.out.println(name);
}
}
调用处理器:
package com.songzhishu.spring6.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @BelongsProject: Spring6
* @BelongsPackage: com.songzhishu.spring6.service
* @Author: 斗痘侠
* @CreateTime: 2023-10-17 16:24
* @Description: 调用处理器,用来计时增强功能
* @Version: 1.0
*/
public class TimeInvocationHandler implements InvocationHandler {
//目标对象
private Object target;
//构造方法 目标对象
public TimeInvocationHandler(Object target) {
//给目标对象赋值
this.target=target;
}
/*
* invoke方法什么时候调用 只有代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法就
* 会被调用
*
* invoke方法里面的参数
* 参数一 代理对象
* 参数二 目标对象的目标方法
* 参数三 目标方法上的实参
*
*
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//使用代理方法添加增强功能
long begin = System.currentTimeMillis();
Object revalue = method.invoke(target, args);
//调用目标对象目标功能
long end = System.currentTimeMillis();
System.out.println("耗费时长"+(end - begin)+"毫秒");
//如果代理对象需要返回值的话,invoke方法必须将目标对象的目标方法的执行结果返回
return revalue; //返回方法的返回值!!!!!
}
}
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。
一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务 核心业务是纵向的!这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。
如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:
用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。
AOP的优点:
通过下图,大家可以很好的理解AOP的相关术语:
切点表达式用来定义通知(Advice)往哪些方法上切入。语法格式:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:可选项
返回值类型:必填项
全限定类名:可选项
方法名:必填项
形式参数列表:必填项
异常:可选项
Spring对AOP的实现包括以下3种方式:
spring配置文件:
目标类:
切面:
好啦这就是大概的流程
通知类型包括:
切面:
package com.songzhishu.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* @BelongsProject: Spring6
* @BelongsPackage: com.songzhishu.spring6.service
* @Author: 斗痘侠
* @CreateTime: 2023-10-17 20:14
* @Description: TODO
* @Version: 1.0
*/
@Component("logAspect")
@Aspect //这个注解表示是一个切面 ,如果没有这个注解就不是切面
public class LogAspect { //切面
//切面等于通知+切点
/*
通知是以方法的形式出现 (方法可以写增强代码)
@before(切点表达式)表示是一个前置通知,然后切点表达式就是可以表示要切入的方法
*/
@Before("execution(* com.songzhishu.spring6.service..*(..))")
public void before() {
System.out.println("前置");
}
//后置
@AfterReturning("execution(* com.songzhishu.spring6.service..*(..))")
public void afterReturningAdvice() {
System.out.println("后置");
}
//环绕 是最大的通知, 在前置之前 在后置之后
@Around("execution(* com.songzhishu.spring6.service..*(..))")
public void surround(ProceedingJoinPoint joinPoint) throws Throwable {
//前
System.out.println("前环绕");
//目标方法
joinPoint.proceed();
//后
System.out.println("后环绕");
}
//异常通知
@AfterThrowing("execution(* com.songzhishu.spring6.service..*(..))")
public void afterThrowing(){
System.out.println("异常通知");
}
//最终通知 finally
@After("execution(* com.songzhishu.spring6.service..*(..))")
public void ultimately(){
System.out.println("最终");
}
}
然后这个是没有出现异常的时候的顺序!
然后我手动的扔出来一个异常后的执行顺序!
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
优先级高的切面:外面
优先级低的切面:里面
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低
定义切点:
//切点重用:
@Pointcut("execution(* com.songzhishu.spring6.service..*(..))")
public void pointCut(){}
同切面重用:
@Before("pointCut()")
public void before() {
System.out.println("日志前置");
}
不同的切面重用:
@Before("com.songzhishu.spring6.service.LogAspect.pointCut()1")
public void beforeAdvice(){
System.out.println("安全前置通知");
}
①获取连接点信息 :
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
//获取连接点的签名信息
String methodName = joinPoint.getSignature().getName();
//获取目标方法到的实参信息
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
②获取目标方法的返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result)
{ String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);}
③获取目标方法的异常
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
使用配置类代替配置文件
@Configuration//代替配置文件
@ComponentScan({"com.songzhishu.spring6.service"})//组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//启用自动代理
public class SpringConfig {
}
测试:
//全注解
@Test
public void test2(){
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(SpringConfig.class);
UserService bean = applicationContext.getBean("userService", UserService.class);
bean.login();
}
什么是事务:
事务的四个处理过程:
事务的四个特性:
Spring实现事务的两种方式:
编程式事务:通过编写代码的方式来实现事务的管理。
声明式事务:(1)基于注解方式(2)基于XML配置方式
配置文件:
在类上添加该注解,该类中所有的方法都有事务。在某个方法上添加该注解,表示只有这个方法使用事务。
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
事务传播行为在spring框架中被定义为枚举类型:
一共有七种传播行为:
REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
隔离级别在spring中以枚举类型存在:
@Transactional(isolation = Isolation.READ_COMMITTED)
数据库中读取数据存在的三大问题:(三大读问题)
事务隔离级别包括四个级别:
读未提交:READ_UNCOMMITTED
读提交:READ_COMMITTED
可重复读:REPEATABLE_READ
序列化:SERIALIZABLE
大家可以通过一个表格来记忆:
隔离级别 |
脏读 |
不可重复读 |
幻读 |
读未提交 |
有 |
有 |
有 |
读提交 |
无 |
有 |
有 |
可重复读 |
无 |
无 |
有 |
序列化 |
无 |
无 |
无 |
@Transactional(timeout = 10)
以上代码表示设置事务的超时时间为10秒。
表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。
默认值-1,表示没有时间限制。
这里有个坑,事务的超时时间指的是哪段时间?(最后一条DML之前的时间!!!)
在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。
@Transactional(readOnly = true)
将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。
该特性的作用是:启动spring的优化策略。提高select语句执行效率。
如果该事务中确实没有增删改操作,建议设置为只读事务。
默认的是只要有异常就会回滚事务,设置运行时异常以及子类都会回滚!!
@Transactional(rollbackFor = RuntimeException.class)
表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。
@Transactional(noRollbackFor = NullPointerException.class)