Spring 的两大特性是 IoC 和 AOP
IoC 负责将对象动态的注入到容器, 从而达到一种需要谁就注入谁, 什么时候需要就什么时候注入的效果. 理解 spring 的 IoC 也很重要.
但是今天主要来和大家讲讲 aop.
AOP 广泛应用于处理一些具有横切性质的系统级服务, AOP 的出现是对 OOP 的良好补充, 用于处理系统中分布于各个模块的横切关注点, 比如事务管理, 日志, 缓存等等.
AOP 实现的关键在于 AOP 框架自动创建的 AOP 代理.
AOP 代理主要分为静态代理和动态代理,
静态代理的代表为 AspectJ;
动态代理则以 Spring AOP 为代表
1,AspectJ
AspectJ 是静态代理的增强, 采用编译时生成 AOP 代理类, 因此也称为编译时增强, 具有更好的性能.
缺点: 但需要使用特定的编译器进行处理
2,Spring AOP
Spring AOP 使用的动态代理, 运行时生成 AOP 代理类, 所谓的动态代理就是说 AOP 框架不会去修改字节码, 而是在内存中临时为方法生成一个 AOP 对象, 这个 AOP 对象包含了目标对象的全部方法, 并且在特定的切点做了增强处理, 并回调原对象的方法.
缺点: 由于 Spring AOP 需要在每次运行时生成 AOP 代理, 因此性能略差一些.
由于 aspectj 的使用还需要使用特定的编译器进行处理, 处理起来有点麻烦. 今天重要来讲解 Spring AOP
Spring AOP 动态代理主要有两种方式, JDK 动态代理和 CGLIB 动态代理.
JDK 动态代理通过反射来接收被代理的类, 并且要求被代理的类必须实现一个接口. JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类.
如果目标类没有实现接口, 那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类. CGLIB(Code Generation Library), 是一个代码生成的类库, 可以在运行时动态的生成某个类的子类(通过修改字节码来实现代理).
注意, CGLIB 是通过继承的方式做的动态代理, 因此如果某个类被标记为 final, 那么它是无法使用 CGLIB 做动态代理的.
jdk 和 cglib 动态代理来共同实现我们的 aop 面向切面的功能.
下面就来用简单的代码来演示下 jdk 和 cglib 动态代理的实现原理.
一, jdk 动态代理实现 AOP 拦截
1, 为 target 目标类定义一个接口 JdkInterface, 这是 jdk 动态代理实现的前提
- /**
- * Created by qcl on 2018/11/29
- * desc: jdk 动态 aop 代理需要实现的接口
- */
- public interface JdkInterface {
- public void add();
- }
2, 用我们要代理的目标类 JdkClass 实现上面我们定义的接口, 我们的实验目标就是在不改变 JdkClass 目标类的前提下, 在目标类的 add 方法的前后实现拦截, 加入自定义切面逻辑. 这就是 aop 的魅力所在: 代码与代码之间没有耦合.
- /**
- * Created by qcl on 2018/11/29
- * desc: 被代理的类, 即目标类 target
- */
- public class JdkClass implements JdkInterface {
- @Override
- public void add() {
- System.out.println("目标类的 add 方法");
- }
- }
3 , 到了关键的一步, 用我们的 MyInvocationHandler, 实现 InvocationHandler 接口, 并且实现接口中的 invoke 方法. 仔细看 invoke 方法, 就是在该方法中加入切面逻辑的. 目标类方法的执行是由 mehod.invoke(target,args)这条语句完成.
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- /**
- * Created by qcl on 2018/11/29
- * desc: 这里加入切面逻辑
- */
- public class MyInvocationHandler implements InvocationHandler {
- private Object target;
- public MyInvocationHandler(Object target) {
- this.target = target;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("before------- 切面加入逻辑");
- Object invoke = method.invoke(target, args);// 通过反射执行, 目标类的方法
- System.out.println("after------- 切面加入逻辑");
- return invoke;
- }
- }
4, 测试结果
- /**
- * Created by qcl on 2018/11/29
- * desc: 测试
- */
- public class JdkTest {
- public static void main(String[] args) {
- JdkClass jdkClass = new JdkClass();
- MyInvocationHandler handler = new MyInvocationHandler(jdkClass);
- // Proxy 为 InvocationHandler 实现类动态创建一个符合某一接口的代理实例
- // 这里的 proxyInstance 就是我们目标类的增强代理类
- JdkInterface proxyInstance = (JdkInterface) Proxy.newProxyInstance(jdkClass.getClass().getClassLoader(),
- jdkClass.getClass()
- .getInterfaces(), handler);
- proxyInstance.add();
- // 打印增强过的类类型
- System.out.println("=============" + proxyInstance.getClass());
- }
- }
执行上面测试类可以得到如下结果
可以看到, 目标类的 add 方法前后已经加入了自定义的切面逻辑, AOP 拦截机制生效了. 再看 class com.sun.proxy.$Proxy0. 这里进一步证明 JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类
二, cglib 动态代理实现 AOP 拦截
1, 定义一个要被代理的 Base 目标类(cglib 不需要定义接口)
- /**
- * Created by qcl on 2018/11/29
- * desc: 要被代理的类
- */
- public class Base {
- public void add(){
- System.out.println("目标类的 add 方法");
- }
- }
2, 定义 CglibProxy 类, 实现 MethodInterceptor 接口, 实现 intercept 方法. 该代理的目的也是在 add 方法前后加入了自定义的切面逻辑, 目标类 add 方法执行语句为 proxy.invokeSuper(object, args)
- import org.springframework.cglib.proxy.MethodInterceptor;
- import org.springframework.cglib.proxy.MethodProxy;
- import java.lang.reflect.Method;
- /**
- * Created by qcl on 2018/11/29
- * desc: 这里加入切面逻辑
- */
- public class CglibProxy implements MethodInterceptor {
- @Override
- public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy)
- throws Throwable {
- System.out.println("before------- 切面加入逻辑");
- methodProxy.invokeSuper(object, args);
- System.out.println("after------- 切面加入逻辑");
- return null;
- }
- }
3, 测试类
- /**
- * Created by qcl on 2018/11/29
- * desc: 测试类
- */
- public class CglibTest {
- public static void main(String[] args) {
- CglibProxy proxy = new CglibProxy();
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(Base.class);
- // 回调方法的参数为代理类对象 CglibProxy, 最后增强目标类调用的是代理类对象 CglibProxy 中的 intercept 方法
- enhancer.setCallback(proxy);
- // 此刻, base 不是单车的目标类, 而是增强过的目标类
- Base base = (Base) enhancer.create();
- base.add();
- Class<? extends Base> baseClass = base.getClass();
- // 查看增强过的类的父类是不是未增强的 Base 类
- System.out.println("增强过的类的父类:"+baseClass.getSuperclass().getName());
- System.out.println("============ 打印增强过的类的所有方法 ==============");
- FanSheUtils.printMethods(baseClass);
- // 没有被增强过的 base 类
- Base base2 = new Base();
- System.out.println("未增强过的类的父类:"+base2.getClass().getSuperclass().getName());
- System.out.println("============= 打印增未强过的目标类的方法 ===============");
- FanSheUtils.printMethods(base2.getClass());// 打印没有增强过的类的所有方法
- }
- }
下面是打印结果
通过打印结果可以看到
cglib 动态的拦截切入成功了
cglib 动态代理的方式是在运行时动态的生成目标类 (Base) 的子类, 并且在目标类现有方法的基础上添加了很多 cglib 特有的方法. 下面贴出用反射打印类所有方法的工具类
- public class FanSheUtils {
- // 打印该类的所有方法
- public static void printMethods(Class cl) {
- System.out.println();
- // 获得包含该类所有其他方法的数组
- Method[] methods = cl.getDeclaredMethods();
- // 遍历数组
- for (Method method : methods) {
- System.out.print(" ");
- // 获得该方法的修饰符并打印
- String modifiers = Modifier.toString(method.getModifiers());
- if (modifiers.length()> 0) {
- System.out.print(modifiers + " ");
- }
- // 打印方法名
- System.out.print(method.getName() + "(");
- // 获得该方法包含所有参数类型的 Class 对象的数组
- Class[] paramTypes = method.getParameterTypes();
- // 遍历数组
- for (int i = 0; i <paramTypes.length; i++) {
- if (i> 0) {
- System.out.print(",");
- }
- System.out.print(paramTypes[i].getName());
- }
- System.out.println(");");
- }
- }
- }
注意: 上面用到了 cglib.jar 和 asm.jar. 在我们的 maven 的 pom.xml 里引入下面类库即可
- <dependency>
- <groupId>
- org.springframework.boot
- </groupId>
- <artifactId>
- spring-boot-starter-aop
- </artifactId>
- </dependency>
来源: http://blog.51cto.com/13981400/2343867