代理模式和面向切面编程
前言:从代理的角度总结Spring AOP
一、静态代理:你不知道我想做什么,我也不关心你做了什么
package simpleproxy; // 设计一个接口 interface Shape { void draw(); void erase(); } // 接口的实现 class Rectangle implements Shape { @Override public void draw() { System.out.println("draw Rectangle"); } @Override public void erase() { System.out.println("erase Rectangle"); } } // 针对接口设计代理,添加代理的逻辑 class SimpleProxy implements Shape { Shape shape; void setShape(Shape shape) { this.shape = shape; } @Override public void draw() { System.out.println("create Rectangle"); shape.draw(); } @Override public void erase() { shape.erase(); System.out.println("destroy Rectangle"); } } public class SimpleProxyDemo { private Shape shape; public SimpleProxyDemo(Shape shape) { this.shape = shape; } public static void main(String[] args) { Rectangle target = new Rectangle(); SimpleProxy proxy = new SimpleProxy(); proxy.setShape(target); SimpleProxyDemo demo = new SimpleProxyDemo(proxy); demo.shape.draw(); demo.shape.erase(); } }
或许大多数时候通过这样的方式实现代理已经足够了,不过这只是故事的开始。
二、JDK动态代理:反射遇上动态编译
JDK动态代理的本质是通过反射形成.java文件,再利用动态编译生成.class文件
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Shape { void draw(); void erase(); } class Rectangle implements Shape { @Override public void draw() { System.out.println("draw Rectangle"); } @Override public void erase() { System.out.println("erase Rectangle"); } } // 把代理的逻辑放在InvocationHandler实现类中,从而可以让任意接口代理相同的逻辑 class Handler implements InvocationHandler { Object targer; Handler(Object targer) { this.targer = targer; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("create Target"); Object o = method.invoke(targer, args); System.out.println("destroy Target"); return o; } } public class DynamicProxyDemo { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); Handler h = new Handler(rectangle); Shape proxy = (Shape) Proxy.newProxyInstance(Rectangle.class.getClassLoader(), Rectangle.class.getInterfaces(), h); proxy.draw(); proxy.erase(); } }
三、CGLIB动态代理:字节码技术的辉煌一笔
需要引入的两个依赖关系
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>4.2</version> </dependency>
当代理对象不是通过接口实现的时候,JDK动态代理就失去了往日的荣光。可是孜孜不倦的程序员们永远不会停住前进的脚步,胜利的旗帜又一次插在了巫妖王的城堡上。
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; // 丢失的Shape接口 class Rectangle { public void draw() { System.out.println("Draw Rectangle"); } public void erase() { System.out.println("Erase Rectangle"); } } // 共同的代理逻辑 class Interceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("create Target"); Object o = proxy.invokeSuper(obj, args); System.out.println("destroy Target"); return o; } } public class CglibDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Rectangle.class); enhancer.setCallback(new Interceptor()); Rectangle proxy = (Rectangle) enhancer.create(); proxy.draw(); proxy.erase(); } }
四、Spring AOP:面向切面的集大成者
无论是JDK的实现方式还是CGLIB的字节码技术,我们都可以利用Spring AOP做最后的统一处理。要说利用Spring AOP创建代理的唯一不足之处就是,无论被代理对象还是代理对象本身都必须交给Spring容器来管理,如果你仅仅是希望实现一个简单的代理逻辑而并不愿意大范围修改代码,引入Spring显然过于笨重。
一个简单的Service接口
package aop; public interface Service { void bar(); }
创建ServiceImpl类实现上述接口
package aop; public class ServiceImpl implements Service { @Override public void bar() { System.out.println("service bar"); } }
编写ServiceB类(没有实现接口的被代理对象)
package aop; public class ServiceB { public void throwException(int type) { System.out.println("service B"); if (type == 1) { throw new IllegalArgumentException("测试异常"); } } }
编写功能齐备的代理逻辑
package aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class AdviceImpl { // 方法前插入 public void doBefore(JoinPoint jp) { System.out.println( "log Begining method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName()); } // 方法后插入 public void doAfter(JoinPoint jp) { System.out.println( "log Ending method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName()); } // 前后一起插入的完整逻辑 public Object doAround(ProceedingJoinPoint pjp) throws Throwable { long time = System.currentTimeMillis(); Object retVal = pjp.proceed(); time = System.currentTimeMillis() - time; System.out.println("process time: " + time + " ms"); return retVal; } // 捕获异常,相当于把被代理对象放置在try...catch中 public void doThrowing(JoinPoint jp, Throwable ex) { System.out.println("method " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + " throw exception"); System.out.println(ex.getMessage()); } }
通过xml文件配置切面、切点和建言。
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"> <aop:config> <aop:aspect id="aspect" ref="aspectBean"> <aop:pointcut id="businessService" expression="execution(* aop.*.*(..))" /> <aop:before pointcut-ref="businessService" method="doBefore" /> <aop:after pointcut-ref="businessService" method="doAfter" /> <aop:around pointcut-ref="businessService" method="doAround" /> <aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex" /> </aop:aspect> </aop:config> <bean id="aspectBean" class="aop.AdviceImpl" /> <bean id="service" class="aop.ServiceImpl" /> <bean id="serviceB" class="aop.ServiceB" /> </beans>
通过junit演示
package aop; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopTest { @Test public void aop() { ApplicationContext ctx = new ClassPathXmlApplicationContext("aop/spring.xml"); // 查看所有对象是否都正确的交给spring来管理 for (String beanName : ctx.getBeanDefinitionNames()) { System.out.println(beanName); } System.out.println("===============伟大的分割线================"); ServiceB serviceB = (ServiceB) ctx.getBean("serviceB"); serviceB.throwException(1); } }
之前我曾经写过一篇博客专门总结过spring官方文档有关aop的章节,感兴趣的朋友可以自己查看。