1、代理模式:
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
为什么使用代理模式:可以增强功能,可以保护代理目标,可以让两个不能直接交互的目标进行交互。
1、静态代理:
初始:
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{@Overridepublic void generate() {try {Thread.sleep(1234);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成");}@Overridepublic void detail() {try {Thread.sleep(2541);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单信息如下:******");}@Overridepublic 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;}@Overridepublic void generate() {//使用代理方法添加增强功能long begin = System.currentTimeMillis();target.generate();//调用目标对象目标功能long end = System.currentTimeMillis();System.out.println("耗费时长"+(end - begin)+"毫秒");}@Overridepublic void modify() {//使用代理方法添加增强功能long begin = System.currentTimeMillis();//调用目标对象目标功能target.modify();long end = System.currentTimeMillis();System.out.println("耗费时长"+(end - begin)+"毫秒");}@Overridepublic void detail() {//使用代理方法添加增强功能long begin = System.currentTimeMillis();//调用目标对象目标功能target.detail();long end = System.currentTimeMillis();System.out.println("耗费时长"+(end - begin)+"毫秒");}
}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
2、动态代理:
程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
- JDK动态代理技术:只能代理接口。
- CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
- Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
公共接口:
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{@Overridepublic void generate() {try {Thread.sleep(1234);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成");}@Overridepublic void detail() {try {Thread.sleep(2541);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单信息如下:******");}@Overridepublic void modify() {try {Thread.sleep(1010);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改");}@Overridepublic 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方法里面的参数* 参数一 代理对象* 参数二 目标对象的目标方法* 参数三 目标方法上的实参*** */@Overridepublic 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; //返回方法的返回值!!!!!}
}
2、面向切面编程AOP:
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。
一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务 核心业务是纵向的!这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。
如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:
- 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
- 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。
用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。
AOP的优点:
- 第一:代码复用性增强。
- 第二:代码易维护。
- 第三:使开发者更关注业务逻辑。
1、AOP的七大术语:
- 连接点 Joinpoint:在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。
- 切点 Pointcut:在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点),本质上就是方法
- 通知 Advice:通知又叫增强,就是具体你要织入的代码 ,连接点的位置,通知包含:
- 前置通知
- 最终通知
- 异常通知
- 环绕通知
- 后置通知
- 切面 Aspect:切点 + 通知就是切面。
- 织入 Weaving: 把通知应用到目标对象上的过程。
- 代理对象 Proxy:一个目标对象被织入通知后产生的新对象。
- 目标对象 Target:被织入通知的对象。
通过下图,大家可以很好的理解AOP的相关术语:
2、切点表达式:
切点表达式用来定义通知(Advice)往哪些方法上切入。语法格式:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:可选项
- 没写,就是4个权限都包括。
- 写public就表示只包括公开的方法。
返回值类型:必填项
- * 表示返回值类型任意。
全限定类名:可选项
- 两个点“..”代表当前包以及子包下的所有类。
- 省略时表示所有的类。
方法名:必填项
- *表示所有方法。
- set*表示所有的set方法。
形式参数列表:必填项
- () 表示没有参数的方法
- (..) 参数类型和个数随意的方法
- (*) 只有一个参数的方法
- (*, String) 第一个参数类型随意,第二个参数是String的。
异常:可选项
- 省略时表示任意异常类型。
3、使用Spring的AOP:
Spring对AOP的实现包括以下3种方式:
- 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
- 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
- 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。
1、基于注解:
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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--组件扫描--><context:component-scan base-package="com.songzhishu.spring6.service"/><!--开启自动代理 proxy-target-class="true" 表示强制实现cglib来生成动态代理 false表示如果接口使用jdk 如果是类使用cglib--><aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
目标类:
切面:
好啦这就是大概的流程
2、通知类型顺序:
通知类型包括:
- 前置通知:@Before 目标方法执行之前的通知
- 后置通知:@AfterReturning 目标方法执行之后的通知
- 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
- 异常通知:@AfterThrowing 发生异常之后执行的通知
- 最终通知:@After 放在finally语句块中的通知
切面:
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("最终");}
}
然后这个是没有出现异常的时候的顺序!
然后我手动的扔出来一个异常后的执行顺序!