Spring-AOP
一、静态代理
-
静态代理,代理类拥有真实角色的引用,同时增强真实角色的方法
-
以结婚为例,模拟静态代理过程:
-
模拟思路:
- 编写一个抽象接口(Marry),统一对外暴露结婚接口(toMarry)
- 编写真实角色类(You)实现Marry的toMarry方法
- 编写代理类(MarryCompany)实现Marry接口,拥有真实的引用,同时在构造方法中实例化真实角色对象,再重写toMarry()方法时,设置增强该toMarry()的方法
-
代码实现:
/** * 方便对外统一接口,让代理角色和真实角色都实现接口方法 */ public interface Marry { /** * 结婚接口 */ void toMarry(); }
/** * 真实角色 */ public class You implements Marry { @Override public void toMarry() { System.out.println("等了这么久,终于等到你。。"); } }
/** * 代理角色,代理的是You */ public class MarryCompany implements Marry { // 引用真实角色-又叫目标对象 private You target; MarryCompany() {} // 采用构造器方式赋值target对象,当然也可以采用setter方法 MarryCompany(You target) { this.target = target; } @Override public void toMarry() { // 编写增强方法 before(); // 调用真实角色的结婚方法 target.toMarry(); // 结婚之后的方法 after(); } private void before() { System.out.println("结婚现场紧张布置中。。。。"); } private void after() { System.out.println("恭喜你进入人生第二阶段。。。。"); } }
public class MarryTest { @Test public void testMarry() { // 创建代理对象 You real = new You(); // 创建真实角色 MarryCompany marryCompany = new MarryCompany(real); // 创建代理对象 marryCompany.toMarry(); } }
-
二、动态代理
1. 概念
在程序运行期,动态创建某个代理对象。
2. JDK反射机制生成
-
编写一个代理生成处理类,实现 InvocationHandler 接口
public class JdkHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
-
编写一个目标对象(真实角色)的引用,是一个Object类型
public class JdkHandler implements InvocationHandler { // 真实角色的引用 private Object target; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
-
编写生成代理对象的方法
public class JdkHandler implements InvocationHandler { // 真实角色的引用 private Object target; /** * 获取代理对象 -- 重点,通过反射机制获取代理对象 * @param target 告诉代理生成器要生成什么样的代理对象 * @return */ public Object getProxy(Object target) { // 传入类加载器、当前对象的所有接口方法、InvocationHandler对象 this.target = target; return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
-
重写invoke方法,设置增强,同时执行代理方法
/** * 基于JDK的代理 */ public class JdkHandler implements InvocationHandler { // 真实角色的引用 private Object target; /** * 获取代理对象 -- 重点,通过反射机制获取代理对象 * @param target 告诉代理生成器要生成什么样的代理对象 * @return */ public Object getProxy(Object target) { // 传入类加载器、当前对象的所有接口方法、InvocationHandler对象 this.target = target; return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), 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 before() { System.out.println("结婚现场紧张布置中。。。"); } private void after() { System.out.println("恭喜你进入人生第二阶段。。。"); } }
-
测试
public class JdkDynamicProxyTest { @Test public void testJdkProxy() { // 真实角色 Marry marry = new You(); JdkHandler jdkHandler = new JdkHandler(); // 生成一个代理对象 Marry marryCompany = (Marry)jdkHandler.getProxy(marry); // 执行接口方法, 会去执行Handler的invoke()方法 marryCompany.toMarry(); } @Test public void testHouseRentProxy() { // 真实角色 RentHouse rentHouse = new Customer(); JdkHandler jdkHandler = new JdkHandler(); // 生成一个代理对象, 会去执行Handler的invoke()方法 RentHouse lianjia = (RentHouse)jdkHandler.getProxy(rentHouse); // 执行接口方法 lianjia.rent(); } }
3. CGLib实现
-
使用Spring的CGLib模块完成动态代理
-
实现步骤:
-
实现 MethodInterceptor 接口
-
编写一个真实角色的引用
-
编写构建代理对象的方法:通过 Enhancer类 进行创建,不需要相关的接口
-
重写拦截请求代理的方法,设置增强
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class DynamicCglibProxyHandler implements MethodInterceptor { // 真是角色的引用 private Object target; /** * 创建代理对象 * @param target 真实角色对象 * @return */ public Object buildProxy(Object target) { this.target = target; // 创建代理对象 Enhancer enhancer = new Enhancer(); // 通过enhancer对象创建代理对象 enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); Object proxy = enhancer.create(); return proxy; } /** * 拦截执行代理的方法 * @param o * @param method * @param objects 执行代理方法的参数 * @param methodProxy 执行代理的方法 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invoke(target, objects);// 其中执行的是真实角色的方法索引应该传入target对象 after(); return result; } private void before() { System.out.println("方法执行前。。。。"); } private void after() { System.out.println("方法执行后。。。。"); } }
- 编写一个StudyJava的类
public class StudyJava { public String study(String name) { System.out.println("好喜欢学JAVA..."); return name + ":好喜欢学JAVA"; } }
- 编写一个测试类
import com.xyz.marry.Marry; import com.xyz.marry.You; import com.xyz.rent.Customer; import com.xyz.rent.RentHouse; import com.xyz.study.StudyJava; import org.junit.Test; public class TestDynamicCGlibProxy { @Test public void testDynamicProxy() { // 创建一个生成器 DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler(); // 创建一个真实角色 Marry marry = new You(); // 创建代理对象 Marry proxy = (Marry) handler.buildProxy(marry); // 执行Marry的toMarry方法 proxy.toMarry(); } @Test public void testRentHouse() { // 创建生成器 DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler(); // 创建租房真实角色 RentHouse rentHouse = new Customer(); // 生成一个代理对象 RentHouse lianjia = (RentHouse)handler.buildProxy(rentHouse); // 执行代理租房方法 String result = lianjia.rent("Nobody"); System.out.println("最终结果:" + result); } @Test public void testStudyJava() { // 创建生成器 DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler(); // 创建真实角色 StudyJava studyJava = new StudyJava(); // 生成一个代理对象 StudyJava banzhang = (StudyJava)handler.buildProxy(studyJava); // 执行代理方法 String result = banzhang.study("Nobody"); System.out.println("最终结果:" + result); } }
-
三、总结
1、静态代理和动态代理的区别
- 静态代理代理的是某一个类或接口;动态代理代理多种接口
- 静态代理的代理类由手动创建即程序运行前创建;动态代理自动创建,在程序运行期获取
2、基于JDK的动态代理和CGlib的区别
- CGLib解决了JDK只能代理接口的局限性
四、AOP编程概念
AOP编程:就是面向切面编程,它是OOP的一种补充,原因是OOP关注的是某个类下的某个方法,而AOP关注的某些类下的某些方法。
五、AOP的应用场景
一些公用的功能模块,比如:日志统计、性能分析、权限控制、事物管理等等。
六、AOP的优点
- 降低模块与模块之间的耦合,提高模块的聚合 — 高内聚、低耦合
- 提高代码的复用性
- 提高了功能的扩展性
七、AOP的基本概念
- 连接点(JoinPoint):被AOP拦截的方法
- 切入点(PointCut):定义AOP拦截连接点的规则
- 通知(Advice):也称作–增强,在连接点执行的基础上设置一些方法:
- 前置通知(before advice):在方法前执行
- 后置通知(after advice):方法执行后执行(不管是正常结束还是抛出异常都会执行)
- 异常通知(exception advice):方法执行中抛出异常会调用
- 返回通知(return advice):方法正常返回结果后调用
- 环绕通知(around advice):拦截连接点,自定义连接点的执行流程
- 切面(Aspect):切入点 + 通知组成
- 决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。
- 目标对象(Target):就是被代理的真实对象
- 织入(Weave):将切面应用到目标对象,同时创建代理对象的过程
- 引入(Introduction):在不修改原有应用程序代码的情况下,在程序运行期动态给连接点添加方法或者属性的过程
八、面向切面编程
AOP 面向切面编程注解实现
使用 Aop 解决日志处理问题
-
引入aspectj语法依赖包
<!--aspectj语法的jar包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
-
开启aop注解驱动
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd <!--开启AOP注解驱动--> <aop:aspectj-autoproxy />
-
编写一个LogAspect切面
- 编写一个LogAspect类,添加@Component注解,标记此类交给Spring管
- 添加@Aspect注解,标记此类是一个切面
@Component // 代表此类交给Spring管理 @Aspect // 标记此类是一个切面 public class LogAspect { }
-
编写一个切入点
- 采用aspectj语法进行定义(AOP 切入点表达式简介):
- 执行任意公共方法:execution(public *(…))
- 执行任意的set方法:execution(* set*(…))
- 执行com.xyz.service包下任意类的任意方法:execution(* com.xyz.service.*.*(…))
- 执行com.xyz.service 包 以及子包下任意类的任意方法:execution(* com.xyz.service…*.*(…))
// 定义一个切入点:就是定义连接点(被AOP拦截的方法)的规则 @Pointcut("execution(* com.xyz.service..*.*(..))") public void pointcut(){}
- 采用aspectj语法进行定义(AOP 切入点表达式简介):
-
编写各种增强
// 定义前置增强 -- 比较常见 @Before("pointcut()") public void before(JoinPoint joinPoint) { // 获取目标对象 Object target = joinPoint.getTarget(); // 获取方法签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取执行方法 Method method = signature.getMethod(); System.out.println("方法名称:" + method.getName()); // 获取参数 Parameter[] parameters = method.getParameters(); System.out.println("前置增强。。。。"); } // 定义后置返回增强 @AfterReturning("pointcut()") public void afterReturning() { System.out.println("后置返回增强。。。"); } // 定义异常增强 @AfterThrowing("pointcut()") public void afterThrowing() { System.out.println("异常增强。。。"); } // 定义最终(后置)增强 @After("pointcut()") public void after() { System.out.println("最终增强。。。"); } // 环绕增强 --- 比较常见 @Around("pointcut()") public Object around(ProceedingJoinPoint joinPoint) { // 获取目标对象 Object target = joinPoint.getTarget(); // 获取方法签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取执行方法 Method method = signature.getMethod(); System.out.println("方法名称:" + method.getName()); // 获取参数 Parameter[] parameters = method.getParameters(); // 执行连接点方法 try { Object result = joinPoint.proceed(); // 获取连接点返回值 return result; } catch (Throwable throwable) { throwable.printStackTrace(); return null; } }
-
测试
public class LogAspectTest { @Test public void testBefore() { ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml"); UserService userService = ac.getBean(UserService.class); User user = new User(); user.setUserName("abc"); user.setPassword("111"); user.setId(1); userService.addUser(user); } /** * 测试返回增强 */ @Test public void testAfterReturning() { ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml"); UserService userService = ac.getBean(UserService.class); userService.updateUser(); } /** * 测试异常增强 */ @Test public void testThrowReturning() { ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml"); UserService userService = ac.getBean(UserService.class); userService.deleteUser(); } /** * 测试环绕增强 */ @Test public void testAround() { ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml"); UserService userService = ac.getBean(UserService.class); String result = userService.updateUser(); System.out.println(result); } }
AOP 面向切面编程 xml 配置
-
先编写一个切面类
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class PermissionAspect { // 定义前置增强 -- 比较常见 public void before(JoinPoint joinPoint) { // 获取目标对象 Object target = joinPoint.getTarget(); // 获取方法签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取执行方法 Method method = signature.getMethod(); System.out.println("方法名称:" + method.getName()); // 获取参数 Parameter[] parameters = method.getParameters(); System.out.println("前置增强。。。。"); } // 定义后置返回增强 public void afterReturning() { System.out.println("后置返回增强。。。"); } // 定义异常增强 public void afterThrowing() { System.out.println("异常增强。。。"); } // 定义最终(后置)增强 public void after() { System.out.println("最终增强。。。"); } // 环绕增强 --- 比较常见 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // 获取目标对象 Object target = joinPoint.getTarget(); // 获取方法签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取执行方法 Method method = signature.getMethod(); System.out.println("方法名称:" + method.getName()); // 获取参数 Parameter[] parameters = method.getParameters(); // 执行连接点方法 Object result = joinPoint.proceed(); // 获取连接点返回值 return result; } }
-
在XML中进行切面、切点、增强的配置
<!--配置一个Aspect Bean--> <bean class="com.xyz.configuration.PermissionAspect" id="permissionAspect"/> <!--配置切面--> <aop:config> <!--关联切面--> <aop:aspect ref="permissionAspect"> <!--定义切入点--> <aop:pointcut id="pointcut" expression="execution(* com.xyz.controller..*.*(..))"/> <!--定义前置通知--> <aop:before method="before" pointcut-ref="pointcut"/> <!--定义返回通知--> <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/> <!--定义异常通知--> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/> <!--定义最终通知--> <aop:after method="after" pointcut-ref="pointcut"/> <!--定义环绕通知--> <aop:around method="around" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>