目录
动态代理利用Java的反射技术(Java Reflection)生成字节码,在运行时创建一个实现某些给定接口的新类(也称"动态代理类")及其实例。
动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)
spring中的AOP是动态代理使用的经典场景。
在基于JDK的动态代理的实现中有两个重要的类:InvocationHandler, Proxy
一个动态代理的示例:
定义一个接口(基于JDK的动态代理只能使用接口)
- public interface ISubject {
- void hello(String param);
- }
为接口定义实现类
- public class SubjectImpl implements ISubject {
-
- @Override
- public void hello(String param) {
- System.out.println("hello " + param);
- }
- }
实现一个代理类:
- public class JDKProxy implements InvocationHandler {
-
- private Object target;
-
- public JDKProxy(Object target) {
- this.target = target;
- }
-
- //创建代理
- public Object newProxy() {
- return (ISubject)Proxy.newProxyInstance(
- target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),
- this);
- }
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-
- System.out.println("---------- 在业务方法调用之前可以进行前置增强 ------------");
-
- //利用反射机制调用方法,invoke为返回值,如果没有返回null
- Object invoke = method.invoke(target, args);
-
- System.out.println("---------- 在业务方法调用之前可以进行后置增强 ------------");
- return invoke;
- }
-
- }
编写代理类实际的调用,利用Proxy类创建代理之后的Subject类
- public class JDKProxyDemo {
-
- public static void main(String[] args) {
-
- ISubject subject = new SubjectImpl();
- JDKProxy subjectProxy = new JDKProxy(subject);
-
- ISubject proxyInstance = (ISubject)subjectProxy.newProxy();
-
- proxyInstance.hello("world");
-
- }
-
- }
运行结果:
- ---------- 在业务方法调用之前可以进行前置增强 ------------
- hello world
- ---------- 在业务方法调用之前可以进行后置增强 ------------
AOP运行原理:目标对象只负责业务逻辑,通知只负责AOP增强逻辑(如日志,数据验证等),而代理对象则将业务逻辑而AOP增强代码组织起来(组织者)
AOP是公用的框架代码放置的理想地方,将公共代码与业务代码分离,使我们在处理业务时可以专心的处理业务。
伪代码:
- public void doSameBusiness (long lParam,String sParam){
- // 记录日志
- log.info("调用 doSameBusiness方法,参数是:"+lParam);
- // 输入合法性验证
- if (lParam<=0){
- throws new IllegalArgumentException("xx应该大于0");
- }
- if (sParam==null || sParam.trim().equals("")){
- throws new IllegalArgumentException("xx不能为空");
- }
- // 异常处理
- try{
- 真正的业务处理
- }catch(...){
- }catch(...){
- }
- // 事务控制
- tx.commit();
- }
通过使用AOP我们可以将日志记录,数据合法性验证,异常处理等功能放入AOP中,那么在编写业务时就可以专心实现真正的业务逻辑代码。
在spring中org.springframework.aop.framework.ProxyFactoryBean用来创建代理对象,在一般情况下它需要注入一下三个属性:
准备工作:创建一个IBookService接口及其实现类,用于演示spring AOP开发示例:
- public interface IBookService {
-
- // 购书
- public boolean buy(String userName, String bookName, Double price);
-
- // 发表书评
- public void comment(String userName, String comments);
-
- }
- public class BookServiceImpl implements IBookService {
-
- private Logger logger = LoggerFactory.getLogger(this.getClass());
-
- public BookServiceImpl() {
- super();
- }
-
- public boolean buy(String userName, String bookName, Double price) {
- //logger.info("userName={},bookName={},price={}", userName, bookName, price);
- // 通过控制台的输出方式模拟购书
- logger.info(userName + " buy " + bookName + ", spend " + price);
- return true;
- }
-
- public void comment(String userName, String comments) {
- logger.info(userName + " say:" + comments);
- }
-
- }
将service配置到spring配置文件中,以便于被spring管理,按自己的实际情况配置
- <bean id="bookServiceTarget" class="com.zking.sp02.impl.BookServiceImpl"/>
前置通知需要实现org.springframework.aop.MethodBeforeAdvice,前置通知将在目标对象调用前调用。示例实现购书系统AOP方式实现日志,简单打印调用的方法及参数
1)首先实现一个前置通知类,实现接口MethodBeforeAdvice,并实现接口中的before方法
- public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
-
- private Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Override
- public void before(Method method, Object[] args, Object target) throws Throwable {
-
- String s = "[前置通知]: "
- + this.getClass() + "."
- + method.getName()
- + "将被调用,参数为:"
- + Arrays.toString(args);
-
- logger.info(s);
- }
-
- }
2)将实现的前置通知配置到Spring.xml中,一遍与被spring管理。需要根据自己的实际情况配置。
<bean id="myMethodBeforeAdvice" class="com.zking.springdemo.aop.MyMethodBeforeAdvice"/>
3)现在需要解决如何将通知和目标联系起来,需要一个组织者 - 代理
- <bean id="bookService" class="org.springframework.aop.framework.ProxyFactoryBean">
- <!-- 配置代理目标 -->
- <property name="target" ref="bookServiceTarget"/>
-
- <!-- 配置拦截器列表,拦截器就是通知 -->
- <property name="interceptorNames">
- <list>
- <value>myMethodBeforeAdvice</value>
- </list>
- </property>
-
- <!-- 代理要实现的接口,代理类与被代理类需要实现相同接口 -->
- <property name="proxyInterfaces">
- <list>
- <value>com.zking.springdemo.aop.IBookService</value>
- </list>
- </property>
- </bean>
写一个测试类,测试前置通知
- public class Demo {
-
- public static void main(String[] args) {
-
- ApplicationContext <u>cxt</u> = new ClassPathXmlApplicationContext("/spring.xml");
- IBookService bookService = (IBookService)cxt.getBean("bookService");
-
- System.out.println(bookService.getClass().getName());
- bookService.buy("zs", "hlm", 10D);
- }
-
- }
在连接点正常完成后执行的通知。定义的后置通知类需要实org.springframework.aop.AfterReturningAdvice
示例:在线购书系统中,要求不修改BookServiceImpl代码的情况下增加如下功能:对买书的用户进行返利:每买本书返利10元,简单打印类似于“[后置通知] 返利10元”即可。开发步骤与前置通知类似
1) 编写一个后置通知实现类
- public class MyAfterReturnAdvice implements AfterReturningAdvice {
-
- private Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Override
- public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
- throws Throwable {
- logger.info("[后置通知]: 返利10元");
- }
- }
2) 将后置通知实现类配置到spring配置文件中,以便于spring管理
<bean id="myAfterReturnAdvice" class="com.zking.springdemo.aop.MyAfterReturnAdvice"/>
3)解决如何将通知和目标联系起来,在实现前置通知时已经配置了代理对象,现在只要将后置通知也配置到拦截器列表当中即可。
- <property name="interceptorNames">
- <list>
-
- <value>myMethodBeforeAdvicevalue>
-
- <value>myAfterReturnAdvicevalue>
- list>
- property>
运行上例已经实现的测试类,查看后置通知的运行效果。
包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能很强大。自定义的环绕通知需要实现org.aopalliance.intercept.MethodInterceptor接口。
示例:在环绕通知中输出日志和返回值
1)实现一个环绕通知,该类要实现MethodInterceptor接口
- public class MyMethodInterceptor implements MethodInterceptor {
-
- private Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Override
- public Object invoke(MethodInvocation invocation) throws Throwable {
-
- //获取目标对象
- Object target = invocation.getThis();
- //获取参数
- Object[] args = invocation.getArguments();
- //获取方法
- Method method = invocation.getMethod();
-
- logger.info("[环绕通知] 前:将调用{}.{}方法,参数为{}",
- target.getClass(),
- method.getName(),
- Arrays.toString(args));
-
- //调用目标对象上的方法
- Object val = invocation.proceed();
-
- logger.info("[环绕通知] 后,已调用{}.{}, 返回值:{}",
- target.getClass(),
- method.getName(),
- val);
-
- return val;
- }
-
- }
2)在spring的配置文件中配置环绕通知,以便于spring管理
<bean id="myMethodInterceptor" class="com.zking.springdemo.aop.MyMethodInterceptor"/>
3)解决如何将通知和目标联系起来,在实现前置通知时已经配置了代理对象,现在只要将环绕通知也配置到拦截器列表当中即可。
- <property name="interceptorNames">
- <list>
-
- <value>myMethodBeforeAdvicevalue>
-
- <value>myAfterReturnAdvicevalue>
-
- <value>myMethodInterceptorvalue>
- list>
- property>
运行上例已经实现的测试类,查看后置通知的运行效果。
异常通知需要实现ThrowsAdvice接口,这个通知会在方法抛出异常退出时执行,与以上演示的前置、后置、环绕通知不同,主要有一下特点:
示例
1)定义一个自定义异常,继承RuntimeException运行时异常
- public class PriceException extends RuntimeException {
-
- public PriceException() {
- super();
- }
-
- public PriceException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public PriceException(String message) {
- super(message);
- }
-
- public PriceException(Throwable cause) {
- super(cause);
- }
- }
2)创建异常通知类,该类实现ThrowsAdvice接口,并实现afterThrowing方法
- public class MyThrowsAdvice implements ThrowsAdvice {
-
- private Logger logger = LoggerFactory.getLogger(this.getClass());
-
- //以异常类型作为参数,无返回值
- public void afterThrowing(PriceException e) {
- logger.info("程序发生了PriceException异常");
- }
-
- }
剩下的步骤是将异常通知配置到spring配置文件中,及在代理配置中加入异常通知的配置,可参考上面的环绕通知等示例。
适配器, 通过正则表达式来定义方法切入点,也就是说定义哪些方法将被拦截器处理。适配器=通知(Advice)+切入点(Pointcut)。
在配置适配器时需要使用org.springframework.aop.support.RegexpMethodPointcutAdvisor
配置适配器示例:
- <!-- 配置适配器 -->
- <bean id="myAdisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
-
- <!-- 定义正则表达式,定义需要拦截的方法名 ,本例定义了所有以buy结尾的方法 -->
- <property name="patterns">
- <list>
- <value>.*buy</value>
- </list>
- </property>
-
- <!-- 定义由那个通知(或者叫拦截器)来处理匹配的方法 -->
- <property name="advice">
- <ref bean="myAfterReturnAdvice"/>
- </property>
- </bean>
在代理中使用刚刚配置的适配器
-
- <value>myAdisorvalue>
修改代理中的拦截器列表(spring配置文件,代理部分),将配置器直接配置在拦截器列表中即可。