1.JDK动态代理
jdk动态代理实现步骤:
前提:jdk动态代理有限制条件,要代理的目标对象必须要实现接口
实现:使用反射API实现,具体实现原理这里不做详细讲解,这里只讲解动态代理的实现。
以下为代码列表,所有涉及到的类有三个
-
Calculator.java 【目标对象实现的接口】
-
CalculatorImpl.java 【目标对象】
-
Main.java 【程序入口类】
//目标对象实现的接口 public interface Calculator { int add(int a, int b); }
//目标对象 public class CalculatorImpl implements Calculator { @Override public int add(int a, int b) { return a + b; } }
//程序入口 import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { //创建目标对象 Calculator calculator = new CalculatorImpl(); //创建代理对象 Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), new Class[]{Calculator.class}, (proxy, method, args1) -> { //定义其他的程序流程 System.out.println("the method " + method.getName() + " is running ..."); //执行目标方法 return method.invoke(calculator, args1); }); o.add(2, 3); } }
这三个类中,接口和目标对象没什么说的,都很简单。核心代码其实就一行
Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), new Class[]{Calculator.class}, (proxy, method, args1) -> { System.out.println("the method " + method.getName() + " is running ..."); return method.invoke(calculator, args1); });
对应的API就是
//返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。 public static Object newProxyInstance(ClassLoader loader,//类加载器 Class<?>[] interfaces,//目标对象实现的接口 InvocationHandler h)//调用处理程序 throws IllegalArgumentException
2.CGLIB动态代理
cglib动态代理不需要目标类继承其它类或者实现接口,利用asm开源包,对目标对象类的class文件加载进来,通过修改其字节码生成子类来处理。实际上cglib的api和jdk动态代理api很类似。
//目标对象,这里没有实现接口,所以用CGLIB代理实现 public class Calculator{ public int add(int a, int b) { return a + b; } }
//程序入口 import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Calculator.class); //public void setCallback(final Callback callback) //这个方法需要Callback回调,这是一个接口,常用的两个实现 //1.MethodInterceptor接口,内部是intercept方法,形参如下: //o为返回的代理对象,不常用,容易抛出堆栈溢出 //method为目标方法的反射类型,可以获取当前执行的目标方法的方法名 //objects为object数组,实际为目标方法的参数列表 //methodProxy用于调用初始方法,或者是调用其它类的同名方法【不同于第二个参数method,不用创建目标对象】 //2.InvocationHandler接口,内部是invoke方法,形参如下: //invoke(java.lang.Object o, java.lang.reflect.Method method, java.lang.Object[] objects) //o为返回的代理对象,不常用,容易抛出堆栈溢出 //method为目标方法的反射类型 //objects为参数列表 enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> { String name = method.getName(); //目标方法 System.out.println("方法" + name + "正在运行"); //目标方法的参数列表 System.out.println("参数列表:"+objects); //Object result = method.invoke(o, objects); //方法抛出堆栈异常,需要创建目标对象作为形参 Object result = methodProxy.invokeSuper(o, objects); System.out.println("执行结果:"+result); return result; }); //创建代理对象 Calculator proxyObject = (Calculator) enhancer.create(); // System.out.println(proxyObject); proxyObject.add(1, 2); } }
3.总结jdk动态代理和cglib代理
jdk动态代理需要目标对象实现接口
cglib代理不需要目标对象实现接口或者继承对象
jdk动态代理使用的是反射的API
cglib代理使用asm解析并修改字节码文件,生成的代理对象本质是目标对象的子类
两种方式都是运行时动态绑定的。
4.aspectj代理实现
Spring AOP本质就是配合使用JDK Proxy动态代理和CGLIB工具,从而实现方法的切入。Spring会优先使用JDK动态代理,当调用方法不是接口方法时,选择使用CGLIB。这一章节使用aspectj的注解配置来实现动态代理。
Spring AOP 和 Aspectj对比文章请参考:https://juejin.im/post/5a695b3cf265da3e47449471
//目标对象 @Component public class Calculator2 { public int add(int a, int b) { return a + b; } }
//切面 @Component @Aspect public class LoggingAspect2 { /** * 定义切入点表达式,方便管理和代码复用 */ @Pointcut(value = "execution(* aspectj.*.*.*(..))") public void declarePointcutExpression() { //此方法内部不能有任何代码,只是用于标记Pointcut注解 } /** * 前置通知,在方法被执行前调用 * * @param joinPoint 连接点 */ @Before(value = "declarePointcutExpression()") public void beforeExecute(JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); System.out.println("前置通知:在" + name + "方法【前】被执行..."); } /** * 后置通知,在方法执行后调用,不论方法是否抛出异常,都会调用 * * @param joinPoint 连接点 */ @After(value = "declarePointcutExpression()") public void afterExecute(JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); System.out.println("后置通知:在" + name + "方法【后】被执行..."); } /** * 返回通知,在方法正常执行完毕,返回后调用,可以获取返回值 * * @param joinPoint 连接点 */ @AfterReturning(value = "declarePointcutExpression()", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { String name = joinPoint.getSignature().getName(); System.out.println("正常返回通知:在" + name + "方法【正常执行返回后通知】被执行..."); System.out.println("----------------------方法返回值:---" + result + "----------"); } /** * 抛出异常通知,在方法抛出异常后执行,可以限定方法在抛出指定的异常之后才执行,可以获取异常信息 * * @param joinPoint 连接点 */ @AfterThrowing(value = "declarePointcutExpression()", throwing = "e") public void afterThrowing(JoinPoint joinPoint, Throwable e) { String name = joinPoint.getSignature().getName(); System.out.println("发出异常通知:在" + name + "方法【抛出异常】被执行"); System.out.println("----------------------获取异常信息:---" + e.toString() + "-----------"); } /** * 环绕通知:在方法的前后都可以定义代码块执行,功能最强 */ @Around(value = "declarePointcutExpression()") public Object around(ProceedingJoinPoint proceedingJoinPoint) { String name = proceedingJoinPoint.getSignature().getName(); System.out.println("环绕通知:在" + name + "方法前后都可以执行代码块"); System.out.println("环绕通知:此为方法调用前的输出日志...."); Object result = null; try { //调用目标对象的对应方法 result = proceedingJoinPoint.proceed(); System.out.println("环绕通知:此为方法调用后的输出日志...."); System.out.println("-----------------------------------环绕通知获取返回值-------" + result + "------------"); } catch (Throwable throwable) { System.out.println("环绕通知:此为方法抛出异常信息之后的输出日志...."); System.out.println("------------------------------------环绕通知获取异常信息-----------" + throwable + "---------------------------"); throwable.printStackTrace(); } System.out.println("环绕通知:此为方法正常返回时的输出日志..."); return result; } }
//测试类 @SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class CalculatorImpl2Test { @Autowired private Calculator2 calculator; @Test public void add() { calculator.add(1, 1); } }
项目源码:https://github.com/lingEric/dynamicproxy