代理模式

Subject(抽象主题角色) :定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
RealSubject(真实主题角色 ):也叫被代理角色/委托类/目标对象,是业务逻辑的具体执行者,真正实现业务逻辑的类;
Proxy(代理主题角色) :也叫代理类/代理对象,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并在真实主题角色处理前后做一些预处理或善后工作。
java
/**
* 抽象主题接口
*/
public interface Subject {
void doSomething();
}
/**
* 真实主题角色
*/
public class RealSubject implements Subject{
@Override
public void doSomething() {
//TODO 具体执行的事
}
}
/**
* 代理主题角色
*/
public class Proxy implements Subject{
//要代理的具体实现类
private Subject realSubject;
public Proxy(Subject realSubject){
this.realSubject = realSubject;
}
@Override
public void doSomething() {
this.before();
realSubject.doSomething();
this.after();
}
// 预处理
private void before(){
// TODO
}
// 善后处理
private void after(){
// TODO
}
}
-
静态代理模式
所谓静态 也就是在程序运行前 就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
优点:
- 通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码.
缺点:
- 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
- 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
- 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护
代码参考:spring-sopstaticproxy包
-
动态代理模式
不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件
-
JDK动态代理
JDK动态代理主要涉及两个类:
java.lang.reflect.Proxy和java.lang.reflect.InvocationHandlerjavapackage com.light.dynamicproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Date; public class DynamicProxyHandler implements InvocationHandler { //保证线程安全且符合封装原则,强制在构造器赋值。 private final Object target; public DynamicProxyHandler(Object target) { this.target = target; } public Object getProxy(){ // 用于定义代理类的类加载器 ClassLoader classLoader = target.getClass().getClassLoader(); // 代理类要实现的一系列接口 Class<?>[] interfaces = target.getClass().getInterfaces(); if (interfaces.length == 0) { throw new IllegalArgumentException("目标对象未实现任何接口,无法使用JDK动态代理,请考虑CGLIB"); } return Proxy.newProxyInstance(classLoader, interfaces, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); after(); return result; } private void after() { System.out.println(String.format("log end time [%s]", new Date())); } private void before() { System.out.println(String.format("log start time [%s]", new Date())); } }测试
javaimport com.light.dynamicproxy.DynamicProxyHandler; import com.light.staticproxy.UserService; import org.junit.jupiter.api.Test; import com.light.staticproxy.UserServiceImpl; import com.light.staticproxy.UserServiceProxyForExtends; import com.light.staticproxy.UserServiceProxyForInterface; import java.lang.reflect.Proxy; public class TestProxy { @Test public void testStaticProxy(){ //基于接口的proxy UserServiceProxyForInterface serviceProxy = new UserServiceProxyForInterface(new UserServiceImpl()); serviceProxy.select(); serviceProxy.update(); //基于继承的proxy UserServiceProxyForExtends proxyForExtends = new UserServiceProxyForExtends(); proxyForExtends.select(); proxyForExtends.update(); } @Test public void testDynamicProxy() { //1.创建具体主题类 UserServiceImpl userServiceImpl = new UserServiceImpl(); //2.获取对应的invocationHandler(用于处理方法调用的处理器) DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(userServiceImpl); //3.获取代理类 UserService proxy = (UserService) dynamicProxyHandler.getProxy(); proxy.select(); proxy.update(); } } -
CGLIB动态代理(通过继承类的方式)
-
maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
javaimport java.lang.reflect.Method; import java.util.Date; public class LogInterceptor implements MethodInterceptor { /** * @param object 表示要进行增强的对象 * @param method 表示拦截的方法 * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用 * @return 执行结果 * @throws Throwable */ @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法 after(); return result; } private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log end time [%s] ", new Date())); } }
-
-
JAVAssist:Javassist(Java编程助手)让Java字节码操作变得简单。它是一个用于在 Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义新类,并在 JVM 加载类文件时修改类文件。
-
概述
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。
AOP的作用:
- 提供声明式事务管理。
- 让用户实现自定义切面。
相关术语
横切关注点
分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点,通俗理解为我们要关注的地方。
为啥叫横切?因为程序是竖向执行,这里我们理解为横向切入。竖向执行某段程序的过程中,横向切入一些程序,让功能更加健壮。
通知(增强)
增强,通俗说,就是你想要增强的功能,比如 安全,事物,日志等。
- 前置通知:在被代理的目标方法前执行某些操作
- 返回通知:在被代理的目标方法成功结束后执行某些操作
- 异常通知:在被代理的目标方法异常结束后执行某些操作
- 后置通知:在被代理的目标方法最终结束后执行某些操作
- 环绕通知:使用try....catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
切面
封装通知方法的类,代理类。
目标
被代理的目标对象,被通知的对象。
代理
向目标对象应用通知之后创建的代理对象。
连接点
这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方

Spring AOP
Spring AOP 默认使用标准的JDK 动态代理来实现 AOP 代理。这使得任何接口(或一组接口)都可以被代理。如果被代理类没有实现任何接口,Spring AOP 也可以使用 CGLIB 代理。
必须引入的包:
xml
<!-- Source: https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.24</version>
</dependency>
springApi接口实现Aop
用户委托和接口类
java
package com.light.aop1;
public interface UserService {
public void add();
public void update();
public void delete();
public void search();
}
package com.light.aop1;
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void search() {
System.out.println("查看了用户");
}
}
通知
java
package com.light.aop1.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
/**
*
* @param method the method being invoked
* @param args the arguments to the method
* @param target the target of the method invocation. May be {@code null}.
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
}
}
package com.light.aop1.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice
{
/**
*
* @param returnValue the value returned by the method, if any
* @param method the method being invoked
* @param args the arguments to the method
* @param target the target of the method invocation. May be {@code null}.
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了, 返回结果为:"+returnValue);
}
}
xml配置
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: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.light.aop1.UserServiceImpl" />
<bean id="afterlog" class="com.light.aop1.log.AfterLog" />
<bean id="beforelog" class="com.light.aop1.log.BeforeLog" />
<!-- 方式1 使用原生spring API接口-->
<!-- 配置aop,导入aop约束-->
<aop:config>
<!--pointcut切入点
id:唯一标识
expression:表达式
execution表达式的完整格式为:execution(修饰符? 返回类型 类名?方法名(参数)),带 ? 的部分可以省略(异常匹配极少使用)
核心通配符只有三个:
- `*` 匹配任意一个名称或类型;
- `..` 在包路径位置表示"包含所有子包"、在参数位置表示"任意个参数";
- `+` 在类型后面表示"含其子类"
-->
<!--expression-->
<aop:pointcut id="pointcut" expression="execution(* com.light.aop1.UserServiceImpl.*(..))"/>
<!-- 执行环绕-->
<aop:advisor advice-ref="afterlog" pointcut-ref="pointcut" />
<aop:advisor advice-ref="beforelog" pointcut-ref="pointcut" />
</aop:config>
</beans>
execution完整语法模板
execution 表达式由几个固定部分按顺序组成,格式如下:
execution(修饰符匹配? 返回类型匹配 声明类型匹配?方法名匹配(参数匹配) 异常匹配?)
带 ? 的部分可以省略(异常匹配极少使用),所以最简形式是 execution(返回类型 方法名(参数))。你选中的这个表达式按位置拆解如下:
| 组成部分 | 选中表达式的值 | 含义 |
|---|---|---|
| 修饰符(可省略) | 未写 | 不限定修饰符 |
| 返回类型(必写) | * |
任意返回类型 |
| 声明类型(可省略) | com.xyz.service.* |
service 包下的所有类 |
| 方法名(必写) | * |
任意方法名 |
| 参数(必写) | (..) |
任意个数、任意类型的参数 |
整句含义就是:匹配 com.xyz.service 包下所有类的所有方法执行。
各部分的写法要点
返回类型只能是 *(任意)或具体类型,如 void、String、java.util.List。
声明类型(类路径)是写法最讲究的部分,关键在于区分 . 和 ..:
com.xyz.service.*:只匹配 service 包下一层的类,不含子包com.xyz.service..*:匹配 service 包及其所有子包下的类com.xyz.service.AccountService:精确匹配某一个类com.xyz.service.AccountService+:匹配该类及其子类*..service.*:任意路径下名为 service 的包
方法名支持任意位置通配:* 表示任意方法,get* 表示以 get 开头的方法,*ByName 表示以 ByName 结尾的方法。
参数部分的写法:
():无参数(..):任意个数(0 到多个)、任意类型(*):恰好一个任意类型的参数(String, ..):第一个参数必须是 String,后面任意(String, Integer):精确匹配两个参数的类型
常用示例速查
| 表达式 | 匹配范围 |
|---|---|
execution(public * *(..)) |
所有 public 方法 |
execution(* set*(..)) |
所有以 set 开头的方法 |
execution(* com.xyz.service.AccountService.*(..)) |
某个类的所有方法 |
execution(* com.xyz.service.*.*(..)) |
service 包下所有类的所有方法 |
execution(* com.xyz.service..*.*(..)) |
service 包及子包下所有类的所有方法 |
execution(String com.xyz.service.*.get*(..)) |
service 包下返回 String 且以 get 开头的方法 |
execution(* com.xyz.service.*.*(String, ..)) |
第一个参数为 String 的方法 |
执行测试
java
import com.light.aop1.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestProxy {
@Test
public void testAop1(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService bean = context.getBean("userservice", UserService.class);
bean.add();
bean.update();
}
}
自定义实现Aop
自定义执行通知
java
package com.light.aop.selfdefind;
public class PrintLog {
void before(){
System.out.println("=======开始执行前======");
}
void after(){
System.out.println("=======执行完成======");
}
}
xml配置
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: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.light.aop.UserServiceImpl" />
<bean id="log" class="com.light.aop.selfdefind.PrintLog" />
<aop:config>
<!-- 自定义切面,ref表示要引用的类-->
<aop:aspect ref="log">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.light.aop.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point" />
<aop:after method="after" pointcut-ref="point" />
</aop:aspect>
</aop:config>
</beans>
注解实现Aop
实现一个切面类
java
package com.light.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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 PointCut {
@Before("execution(* com.light.annotation.UserServiceImpl.*(..))")
public void before(){
System.out.println("开始执行之前");
}
@After("execution(* com.light.annotation.UserServiceImpl.*(..))")
public void after(){
System.out.println("执行结束");
}
@Around("execution(* com.light.annotation.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//获取签名
Signature signature = jp.getSignature();
System.out.println("signatrue:" + signature);
Object proceed = jp.proceed();//执行方法
System.out.println("环绕后");
System.out.println(proceed);
}
}
xml配置
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: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="user-service" class="com.light.annotation.UserServiceImpl" />
<bean id="pointcut" class="com.light.annotation.PointCut" />
<!--开启注解的支持-->
<!--proxy-target-class 默认false 使用jdk反射实现注解-->
<!-- 改成true,使用cglib实现注解-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
测试
java
import com.light.annotation.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestProxy {
@Test
public void testAopForAnnotation(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService bean = context.getBean("user-service", UserService.class);
bean.add();
bean.update();
}
}