spring AOP
什么叫AOP?
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。
实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。 但是人们也发现,在分散代码的同时,也增加了代码的
重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象
的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。 也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两
个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置
上的编程思想就是面向切面的编程。 一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一
个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP
变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。
1.代理模式readMe:
1 代理设计模式: 是java中常用的设计模式! 2 3 特点: 4 01.委托类和代理类有相同的接口或者共同的父类! 5 02.代理类为委托类负责处理消息,并将消息转发给委托类! 6 03.委托类和代理类对象通常存在关联关系! 7 一个代理类对象与一个委托类对象关联! 8 04.代理类本身并不是真正的实现者!而是通过调用委托类的方法, 9 来实现功能! 10 11 12 按照代理类创建的时机,代理类分为两种: 13 01.静态代理:由我们程序猿或者特定的工具自动生成了源代码, 14 在程序运行之前,.class文件已经存在了! 15 (serviceImpl 调用了 dao层的方法! 真正的实现是Dao) 16 02.动态代理:在程序运行期间,通过反射的方式动态的创建出来! 17 18 19 按照我们的使用方式: 是由共同的接口还是公共的父类? 20 21 01.jdk动态代理 (接口) 22 必须知道一个类和一个接口 23 001.InvocationHandler接口只有一个方法 24 25 public Object invoke(Object proxy, Method method, Object[] args) 26 throws Throwable; 27 28 proxy:代理类对象 29 method:被代理的方法 30 args:被代理的方法的参数列表 31 32 002.Proxy 类: 33 public static Object newProxyInstance(ClassLoader loader, 34 Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException 35 loader:类加载器 36 interfaces:代理类实现的所有接口 37 h:InvocationHandler接口的一个实例 this当前对象 38 因为我们想使用jdk动态代理 必须是 代理类 实现 InvocationHandler! 39 它让我们传递父接口 我们传递 自身! 40 41 02.cglib动态代理(接口+父类) 42 必须知道一个类和一个接口 43 001.MethodInterceptor接口 44 45 public Object intercept(Object obj, 46 Method method,Object[] args,MethodProxy proxy) throws Throwable; 47 48 intercept是所有拦截器执行的方法,类似于jdk动态代理中的invoke 49 50 51 002. Enhancer类 52 53 设置委托类和代理类的公共接口或者公共的父类 54 public void setSuperclass(Class superclass) { 55 if (superclass != null && superclass.isInterface()) { 56 setInterfaces(new Class[]{ superclass }); 57 } else if (superclass != null && superclass.equals(Object.class)) { 58 // affects choice of ClassLoader 59 this.superclass = null; 60 } else { 61 this.superclass = superclass; 62 } 63 } 64 65 代理类执行完毕 通知委托类 66 public void setCallback(final Callback callback) { 67 setCallbacks(new Callback[]{ callback }); 68 } 69 70 71 在Enhancer类的父类AbstractClassGenerator中有一个方法 72 创建我们需要的代理类 73 protected Object create(Object key)
2.静态代理:
01.接口代码:
package cn.pb.dao; /** * 动物类 父接口 */ public interface Animal { //主业务 void eat(); void sleep(); }
02.实现类代码:
package cn.pb.dao.impl; /** * 狗狗类 实现了Animal接口 */ import cn.pb.dao.Animal; public class Dog implements Animal { public void eat() { System.out.println("狗狗在啃骨头!"); } public void sleep() { System.out.println("狗狗在午休!"); } }
03.静态代理类:
package cn.pb.staticproxy; import cn.pb.dao.Animal; import cn.pb.dao.impl.Dog; /** * 狗狗的静态代理类 */ public class AnimalStaticProxy implements Animal { private Dog dog; public void sleep() { System.out.println("主人在召唤"); //系统级业务 dog.sleep(); System.out.println("主人离开"); //系统级业务 } public void eat() { System.out.println("主人在召唤"); //系统级业务 dog.eat(); System.out.println("主人离开"); //系统级业务 } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } /** * 我们发现的问题 * 01:代码冗余 * 02:把冗余的代码提取成公共的方法 * 03:有可能小猫咪也有这些方法 * 04:提取成一个工具类中的方法 * 05:现在有一个小猫咪 也需要执行 sleep和eat 以及系统级业务方法 * 06:我们又得创建一个小猫咪对应的代理类 * 07:动物有很多 ,难道需要我们创建N个代理类吗??肯定! */ }
04.测试类代码:
/** * 静态代理的测试方法 */ @Test public void testStaticProxy(){ AnimalStaticProxy proxy = new AnimalStaticProxy(); Dog dog=new Dog(); proxy.setDog(dog); proxy.eat(); System.out.println("*************"); proxy.sleep(); }
3.JDK动态代理:
01.接口代码:
package cn.pb.dao; /** * 动物类 父接口 */ public interface Animal { //主业务 void eat(); void sleep(); }
02.实现类代码:
package cn.pb.dao.impl; /** * 狗狗类 实现了Animal接口 */ import cn.pb.dao.Animal; public class Dog implements Animal { public void eat() { System.out.println("狗狗在啃骨头!"); } public void sleep() { System.out.println("狗狗在午休!"); } }
03.动态代理类代码:
package cn.pb.jdkdynamicproxy; /** * JDK的动态代理类 */ import cn.pb.dao.Animal; import cn.pb.dao.impl.Dog; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class AnimalJdkDynamicProxy implements InvocationHandler { /** * 01.我们不确定委托类是谁?委托类的类型 是Object * 和委托类建立关联关系 */ private Object target; /** * 02.给我一个委托类,我返回一个代理类对象 */ public Object createProxy(Object target){ //根据传递的参数 进行对象的关联 this.target=target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } /** * * @param proxy :代理对象 * @param method :方法名 * @param args : 参数列表 * @return */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("主人在召唤"); //系统级业务 开始事务 Object result= method.invoke(target,args); // 主业务 System.out.println("主人离开"); //系统级业务 日志处理 关闭事务 return result; } //创建测试方法 public static void main(String[] args) { AnimalJdkDynamicProxy proxy=new AnimalJdkDynamicProxy(); Animal dog= (Animal) proxy.createProxy(new Dog()); dog.eat(); System.out.println("**************************"); dog.sleep(); } }
04.测试代码:
@Test public void testJdkDynamicProxy(){ AnimalJdkDynamicProxy proxy=new AnimalJdkDynamicProxy(); Animal dog= (Animal) proxy.createProxy(new Dog()); dog.eat(); System.out.println("**************************"); dog.sleep(); }
4.cglib动态代理:
01.接口代码:
package cn.pb.dao; /** * 动物类 父接口 */ public interface Animal { //主业务 void eat(); void sleep(); }
02.实现类代码:
package cn.pb.dao.impl; /** * 狗狗类 实现了Animal接口 */ import cn.pb.dao.Animal; public class Dog implements Animal { public void eat() { System.out.println("狗狗在啃骨头!"); } public void sleep() { System.out.println("狗狗在午休!"); } }
03.动态代理类代码:
package cn.pb.cglibdynamicproxy; /** * Cglib动态代理 */ import cn.pb.dao.Animal; import cn.pb.dao.impl.Dog; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class AnimalCglibDynamicProxy implements MethodInterceptor { /** * 在enhancer中有一个setCallBack(this) * 这样就实现了代理类和委托类的关联 */ private Enhancer enhancer=new Enhancer(); /** * 创建代理类对象 */ public Object createProxy(Class clazz){ //设置公共的接口或者公共的类 enhancer.setSuperclass(clazz); //建立关联关系 enhancer.setCallback(this); return enhancer.create(); } /** * 类似于我们jdk中的invoke */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("主人在召唤"); //系统级业务 开始事务 Object result= proxy.invokeSuper(obj,args); // 主业务 System.out.println("主人离开"); //系统级业务 日志处理 关闭事务 return result; } //创建测试方法 public static void main(String[] args) { AnimalCglibDynamicProxy proxy=new AnimalCglibDynamicProxy(); //这里的参数可以传三种形式01:new Dog().getClass() // 02:Class.forName("cn.pb.dao.impl.Dog") 03.Dog.class Animal dog= (Animal) proxy.createProxy(new Dog().getClass()); dog.eat(); System.out.println("**************************"); dog.sleep(); } }
04.测试代码:
@Test public void testCglibDynamicProxy(){ AnimalJdkDynamicProxy proxy=new AnimalJdkDynamicProxy(); Animal dog= (Animal) proxy.createProxy(new Dog()); dog.eat(); System.out.println("**************************"); dog.sleep(); }
就是在方法前或后要做的一些事情。AOP中官方叫法为“切面” 如果写到方法中。后期维护很难。
1:核心代码
1 package com.spring.aop; 2 3 public interface ArithmeticCalculator { 4 5 int add(int i, int j); 6 int sub(int i, int j); 7 8 int mul(int i, int j); 9 int div(int i, int j); 10 11 }
1 package com.spring.aop; 2 3 import org.springframework.stereotype.Component; 4 5 @Component("arithmeticCalculator") 6 public class ArithmeticCalculatorImpl implements ArithmeticCalculator { 7 8 public int add(int i, int j) { 9 int result = i + j; 10 return result; 11 } 12 13 public int sub(int i, int j) { 14 int result = i - j; 15 return result; 16 } 17 18 public int mul(int i, int j) { 19 int result = i * j; 20 return result; 21 } 22 23 public int div(int i, int j) { 24 int result = i / j; 25 return result; 26 } 27 28 }
2:写一个切面的类
package com.spring.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 可以使用 @Order 注解指定切面的优先级, 值越小优先级越高
*/
@Order(1)
@Aspect
@Component
public class LoggingAspect {
/**
* 定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码.
* 使用 @Pointcut 来声明切入点表达式.
* 后面的其他通知直接使用方法名来引用当前的切入点表达式.
*/
@Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public void declareJointPointExpression(){}
/**
* 在 com.atguigu.spring.aop.ArithmeticCalculator 接口的每一个实现类的每一个方法开始之前执行一段代码
*/
@Before("declareJointPointExpression()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object [] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
/**
* 在方法执行之后执行的代码. 无论该方法是否出现异常
*/
@After("declareJointPointExpression()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends");
}
/**
* 在方法法正常结束受执行的代码
* 返回通知是可以访问到方法的返回值的!
*/
@AfterReturning(value="declareJointPointExpression()",
returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends with " + result);
}
/**
* 在目标方法出现异常时会执行的代码.
* 可以访问到异常对象; 且可以指定在出现特定异常时在执行通知代码
*/
@AfterThrowing(value="declareJointPointExpression()",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " occurs excetion:" + e);
}
/**
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
* 且环绕通知必须有返回值, 返回值即为目标方法的返回值
*/
/*
@Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
//执行目标方法
result = pjd.proceed();
//返回通知
System.out.println("The method " + methodName + " ends with " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method " + methodName + " occurs exception:" + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method " + methodName + " ends");
return result;
}
*/
}
3:配置文件中配置上使用注解和AOP
<?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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd "> <!-- 自动扫描的包 --> <context:component-scan base-package="com.spring.aop"></context:component-scan> <!-- 使 AspectJ 的注解起作用 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
***:小知识:先执行哪个切面类
@Order(1) 值越小越先 执行
4:基于xml文件配置的方式
<!-- 配置目标方法的类的bean --> <bean id="arithmeticCalculator" class="com.spring.aop.ArithmeticCalculatorImpl" ></bean> <!-- 配置切面类的bean --> <bean id="laoggingAspect" class="com.spring.aop.LoggingAspect" ></bean> <!-- 配置AOP --> <aop:config> <!-- 切点表达式 就是配置哪个方法是切面 --> <aop:pointcut expression="execution(* com.spring.aop.ArithmeticCalculator.*(int, int))" id="pointcut"/> <!-- 调用方法的时候先执行切面 --> <aop:aspect ref="laoggingAspect"> <aop:before method="beforeMethod" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>