• 0  前言
  • 1  动态代理
    • 1.1 JDK动态代理
    • 1.2 CGLIB动态代理
      • 1.2.1 CGLIB的代理用法
      • 1.2.2 CGLIB的过滤功能
  • 2  Spring AOP中的动态代理机制
    • 2.1 JdkDynamicAopProxy
    • 2.2 CglibAopProxy
  • 3 总结

0  前言

前一个季度旅游TDC的Thames服务有几次宕机,根据组内原因认真查找发现是数据库事务造成的,后来把服务中的事务配置全部去掉,服务恢复正常。根据这次教训,虽然现在还是很难确定是哪一个方面的真正原因,但是激发了我学习Spring事务方面的兴趣。而Spring事务的实现是根据AOP来实现的,对于我这个小菜鸟,只能一步一步来了,决定先从Spring的AOP开始。

1  动态代理

Spring AOP中使用了两种动态代理,一种是JDK的动态代理,一种CGLIB的动态代理。JDK的动态代理必须指定接口,这些接口都是已经被代理对象实现了的;而CGLIB代理则不需要指定接口。

1.1 JDK动态代理

JDK的动态代理网上有很多资料,这里只说我自己的理解。

JDK动态代理必须实现InvocationHandler接口,然后通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)获得动态代理对象。

示例:

public interface Service {
public void help();
public void hello(String str);
} public interface Item {
void test();
} public class ServiceImpl implements Service, Item {
@Override
public void help() {
System.out.println("I want to buy some book");
} @Override
public void hello(String str) {
System.out.println("hello: " + str);
} @Override
public void test() {
System.out.println("Test dynamic proxy.");
}
} public class DynamicProxy implements InvocationHandler {
//这个就是我们要代理的真实对象
private Object service; //构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object service) {
this.service = service;
} @Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before buy some book"); System.out.println("Method:" + method); //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(service, args); //在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after buy some book"); return null;
}
} public class Client {
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true); //要代理的真实对象
Service service = new ServiceImpl(); //要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
InvocationHandler handler = new DynamicProxy(service); //添加以下的几段代码, 就可以将代理生成的字节码保存起来
try {
Field field = System.class.getDeclaredField("props");
field.setAccessible(true);
Properties props = (Properties) field.get(null);
props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Package pkg = Client.class.getPackage();
if (pkg != null) {
String packagePath = pkg.getName().replace(".", File.pathSeparator);
new File(packagePath).mkdirs();
} Service serviceProxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(), handler); System.out.println(serviceProxy.getClass().getName());
serviceProxy.help();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} }
} //生成的字节码文件$Proxy01.class反编译后的代码:
public final class $Proxy0 extends Proxy implements Service, Item {
private static Method m1;
private static Method m5;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0; public $Proxy0(InvocationHandler var1) throws {
super(var1);
} public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final void test() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final void help() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final void hello(String var1) throws {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m5 = Class.forName("com.meituan.proxy.Item").getMethod("test", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.meituan.proxy.Service").getMethod("help", new Class[0]);
m4 = Class.forName("com.meituan.proxy.Service").getMethod("hello", new Class[]{Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

所有方法都会被导向调用InvocationHandler接口的唯一的方法invoke,这个就是我们在被代理对象前后插入相关逻辑的地方。

1.2 CGLIB动态代理

1.2.1 CGLIB的代理用法

使用CGLib动态代理,被代理类不需要强制实现接口。

CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

示例:

public class Biz {
public void add() {
System.out.println("这是一个普通方法...加法。");
} public void minus() {
System.out.println("这是一个普通方法...减法。");
}
} public class BizInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("BizInterceptor, 调用开始 method: " + method +
", methodProxy: " + methodProxy);
methodProxy.invokeSuper(o, objects);
System.out.println("BizInterceptor, 调用结束");
return null;
}
} public class BizCglibClient {
private Object target; /**
* 创建代理对象
*
* @param target
* @return
*/
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
/*Class<?> clazz[] = {Service.class};
enhancer.setInterfaces(clazz);*/
//回调方法
Callback[] obj = {new BizInterceptor(), new BizInterceptor2()};
enhancer.setCallbacks(obj);
enhancer.setCallbackFilter(new BizClassFilter());
//创建代理对象
return enhancer.create();
} public static void main(String[] args) {
try {
Field field = System.class.getDeclaredField("props");
field.setAccessible(true);
Properties props = (Properties) field.get(null);
props.put(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lanhongzhong/Workspace/LuceneTest"); /*Package pkg = Client.class.getPackage();
if (pkg != null) {
String packagePath = pkg.getName().replace(".", File.pathSeparator);
new File(packagePath).mkdirs();
}*/
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} BizCglibClient cglib = new BizCglibClient();
Biz bizCglib = (Biz) cglib.getInstance(new Biz());
bizCglib.add(); bizCglib.minus();
}
}

intercept方法中的参数:Object obj为由CGLib动态生成的代理类实例,Method method为被代理类中的方法引用,Object[] args为参数值列表,MethodProxy为生成的代理类对方法的代理引用。

1.2.2 CGLIB的过滤功能

CallbackFilter可以实现不同的方法使用不同的回调方法,用于分发不同的拦截器。

示例:

//结合1.2.1中的代码,添加如下代码
public class BizInterceptor2 implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("BizInterceptor2, 调用开始 method: " + method +
", methodProxy: " + methodProxy);
methodProxy.invokeSuper(o, objects);
System.out.println("BizInterceptor2, 调用结束");
return null;
}
} public class BizClassFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if (method.getName().equals("minus")) {
return 1;
} return 0;
}
}

2  Spring AOP中的动态代理机制

Spring AOP中的代理根据被代理对象是否实现了接口选择不同的生成代理对象的方式,即第1部分中的两个情况。

2.1 JdkDynamicAopProxy

如果被代理对象实现了需要被代理的接口,则使用JDK的动态代理,在Spring AOP中对应的包装类为JdkDynamicAopProxy。

JdkDynamicAopProxy类实现了InvocationHandler接口,将被代理对象和拦截器作为参数传入,然后生成代理对象。

当代理对象被调用时,JdkDynamicAopProxy的invoke方法作为Proxy对象的回调函数而被触发,从而通过invoke的具体实现,来完成对目标对象方法调用的拦截或者说功能增强的工作。

在invoke方法中,设置了包括获取目标对象、拦截器链,同时把这些对象作为输入,创建了ReflectiveMethodInvocation对象,通过这个ReflectiveMethodInvocation对象来完成对AOP功能实现的封装。在这个invoke方法中,包含了一个完整的拦截器链对目标对象的拦截过程,比如获得拦截器链并对其中的拦截器进行配置,逐个运行拦截器链里的拦截增强,直到最后对目标对象方法的运行。具体可以看源代码。

//我只是搬运工

/**
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource;
Class targetClass = null;
Object target = null; try {
... ... // May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
} // Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
} ... ... return retVal;
}
}

2.2 CglibAopProxy

Cglib2AopProxy的intercept回调方法的实现和JdkDynamicAopProxy的回调实现是非常类似的,只是在Cglib2AopProxy中构造的是CglibMethodInvocation对象来完成拦截器链的调用,而在JdkDynamicAopProxy中是通过构造ReflectiveMethodInvocation对象来完成这个功能的。

3 总结

Spring AOP的核心实现原理就是采用的动态代理,根据被代理对象是否实现了所要被代理的接口这个条件,动态代理会选择不同的实现方案。本文只是尽我所能简单的拿着各方资料来了一个汇总,是一个自己的学习总结。对于Spring AOP的设计架构是我下一步的学习目标。

参考文献

1、http://blog.csdn.net/mhmyqn/article/details/48474815

2、《Spring技术内幕:深入解析Spring架构与设计原理》    许文柯

Spring AOP中的动态代理的更多相关文章

  1. 转:Spring AOP中的动态代理

    原文链接:Spring AOP中的动态代理 0  前言 1  动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2  S ...

  2. Spring Boot实践——Spring AOP实现之动态代理

    Spring AOP 介绍 AOP的介绍可以查看 Spring Boot实践——AOP实现 与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改 ...

  3. spring---aop(4)---Spring AOP的CGLIB动态代理

    写在前面 前面介绍了Spring AOP的JDK动态代理的过程,这一篇文章就要介绍下Spring AOP的Cglib代理过程. CGLib全称为Code Generation Library,是一个强 ...

  4. Spring AOP实现原理-动态代理

    目录 代理模式 静态代理 动态代理 代理模式 我们知道,Spring AOP的主要作用就是不通过修改源代码的方式.将非核心功能代码织入来实现对方法的增强.那么Spring AOP的底层如何实现对方法的 ...

  5. spring---aop(2)---Spring AOP的JDK动态代理

    写在前面 spring 事务是springAOP 的一个实现.我们以分析spring的事务,来分析spring的AOP实现. 基本知识 如果目标方法被spring的事务声明,则执行该目标方法的对象就会 ...

  6. Spring AOP系列(二) — 动态代理引言

    接上一篇Spring AOP系列(一)- 代理模式,本篇来聊聊动态代理. 动态代理与静态代理的区别 要想了解动态代理与静态代理的区别,需要有两个前置知识点:java程序是如何执行的以及类加载机制. j ...

  7. Spring AOP关于cglib动态代理

    一: Spring AOP的默认代理方式是jdk动态代理,还有另外一种代理方式是cglib代理,简单说前者基于接口,后者基于继承,基本思路是将被代理对象的类作为父类,然后创建子类来进行方法的调用,调用 ...

  8. Spring AOP系列(三) — 动态代理之JDK动态代理

    JDK动态代理 JDK动态代理核心是两个类:InvocationHandler和Proxy 举个栗子 为便于理解,首先看一个例子: 希望实现这样一个功能:使用UserService时,只需关注自己的核 ...

  9. Spring AOP源码分析--代理方式的选择

    能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! 年前写了一个面试突击系列的文章,目前只有redis相关的.在这个系列里,我整理了一些面试题与大家 ...

随机推荐

  1. sql语句优化之not in

    多表关联想查a表中除去b表的可用not exists 效率比not in 更高 优化后的语句用时0.421秒 select john.*, (case when round((case john.su ...

  2. SpringMVC轻松学习-注解的使用(三)

    根据上一讲的例子,我们下面就注解的使用进行详细说明. 我们采用sprng MVC开发项目时,通常都会采用注解的方式,这样可以大大提高我们的开发效率.实现零配置.下面我们从零开始重新做一个spring ...

  3. Python正则表达式学习笔记

    [] 字符类,只要匹配里面的任意字符,都算匹配 . 元字符,可以匹配除换行符之外的所有字符 大小写敏感,但是可以关闭 \d  可以匹配0-9中的任意数字 {3}大括号里面的数字,边上前面一个字符匹配的 ...

  4. Java 之final,static小结

    一.final 1.final变量: 当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引 ...

  5. SDOI2016 R1 解题报告 bzoj4513~bzoj4518

    储能表 将n, m分解为二进制,考虑一个log(n)层的trie树,n会在这颗trie树上走出了一个路径,因为 行数 $ \le n$,所以在n的二进制路径上,每次往1走的时候,与m计算贡献,m同样处 ...

  6. Lumen 配置、重写、404错误等

    1.权限是否有设置为777: 2.app key是否有设置: 3.apache下是否有开启重写,a2enmod: 4.nginx下server的location是否配置正确,重写规则有没添加,rout ...

  7. Html标签中Alt和Title都是提示性语言标签

    在Html标签中Alt和Title都是提示性语言标签,在我们浏览一些网页时,鼠标停留在一张图片或文字链接上时,在鼠标的右下角出现一个提示信息框,对目标进行一定的注释说明,这就是它们的作用.    其中 ...

  8. Android面试题随笔1

    1.如何让一个应用在手机上产生两个或多个图标? 在清单文件中的activity节点下配置如下:[5,7行代码] <activity android:name=".MainActivit ...

  9. iOS开发——关于开发者账号引发的血案

    这里不介绍怎么申请开发者账号,那个网上的教程太多了.这里讲点有意思的. 如果你们公司比较,怎么说呢,呵呵?管理层不懂开发,不管事,申请开发者账号的人员又比较小白,或者别有用心,用私人邮箱来申请,申请的 ...

  10. 微信小程序-未接入app.json错误

    微信小程序建立新项目之后会出现app.json文件未接入错误如下图: 一般是因为在下图添加新项目,项目目录这一列,如果不事先建立一个空的文件夹,直接选择则不会出现quickstartup界面 所以在建 ...