,好记性不如烂笔头
面向方面的编程(AOP) 是一种编程范式,旨在通过允许横切关注点的分离,提高模块化。
package com.invicme.apps.aop.proxy;import org.apache.log4j.Logger;/** * * @author lucl * * 数学计算实现类 * */public class ArithmeticCalculateImpl implements ArithmeticCalculate { private static final Logger logger = Logger.getLogger(ArithmeticCalculateImpl.class); private int i = 0; private int j = 0; public ArithmeticCalculateImpl () { this(0, 0); } public ArithmeticCalculateImpl (int i, int j) { this.i = i; this.j = j; } @Override public int add(int i, int j) { logger.info("The method add was invoke with args [" + i + ", " + j + "]"); int sum = i + j; logger.info("The method add ends with result [" + sum + "]"); return sum; } @Override public int div(int i, int j) throws DivisorIsZeroException { logger.info("The method div was invoke with args [" + i + ", " + j + "]"); if (j == 0) { throw new DivisorIsZeroException("除数不可为0"); } int result = i / j; logger.info("The method div ends with result [" + result + "]"); return result; } @Override public String validateNum(String level, int i) { logger.info("The method validateNum was invoke with args [" + level + ", " + i + "]"); String result = this.getMsg(i); logger.info("The method validateNum ends with result [" + result + "]"); return result; } private String getMsg (int i) { if (i > 0) { return "正数"; } return "负数"; }}
对于类ArithmeticCalculateImpl的日志通过在方法中加入logger.info来记录的,存在大量的冗余代码,特别是如果在项目开发阶段未通过这种方式记录日志,那么希望记录日志时需要修改代码加入日志逻辑(OOP开发模式)。
AOP为开发者提供一种进行横切关注点分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上Joinpoint还可以是Field或类构造器。Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。Advice(通知):所谓通知是指拦截到Joinpoint之后所要做的事情。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。Target(目标对象):代理的目标对象。Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入。Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。Spring提供了4种实现AOP的方式:
经典的基于代理的AOP
@AspectJ注解驱动的切面
纯POJO切面
注入式AspectJ切面
经典的基于代理的AOP:
Spring支持五种类型的通知:Before(前) org.apringframework.aop.MethodBeforeAdviceAfter-returning(返回后) org.springframework.aop.AfterReturningAdviceAfter-throwing(抛出后) org.springframework.aop.ThrowsAdviceArround(周围) org.aopaliance.intercept.MethodInterceptorIntroduction(引入) org.springframework.aop.IntroductionInterceptor
实现步骤:
1.创建通知:实现这几个接口,把其中的方法实现了
2.定义切点和通知者:在Spring配制文件中配置这些信息3.使用ProxyFactoryBean来生成代理。业务代码(接口):
package com.invicme.apps.aop.proxy;/** * * @author lucl * * 数学计算接口类 * */public interface ArithmeticCalculate { public int add (int i, int j); public int div (int i, int j) throws DivisorIsZeroException; public String validateNum (String level, int i);}
业务代码(实现类):
package com.invicme.apps.aop.proxy;import org.apache.log4j.Logger;/** * * @author lucl * * 数学计算实现类 * */public class ArithmeticCalculateImpl implements ArithmeticCalculate { private static final Logger logger = Logger.getLogger(ArithmeticCalculateImpl.class); private int i = 0; private int j = 0; public ArithmeticCalculateImpl () { this(0, 0); } public ArithmeticCalculateImpl (int i, int j) { this.i = i; this.j = j; } @Override public int add(int i, int j) { logger.info("The method add was invoke with args [" + i + ", " + j + "]"); int sum = i + j; logger.info("The method add ends with result [" + sum + "]"); return sum; } @Override public int div(int i, int j) throws DivisorIsZeroException { logger.info("The method div was invoke with args [" + i + ", " + j + "]"); if (j == 0) { throw new DivisorIsZeroException("除数不可为0"); } int result = i / j; logger.info("The method div ends with result [" + result + "]"); return result; } @Override public String validateNum(String level, int i) { logger.info("The method validateNum was invoke with args [" + level + ", " + i + "]"); String result = this.getMsg(i); logger.info("The method validateNum ends with result [" + result + "]"); return result; } private String getMsg (int i) { if (i > 0) { return "正数"; } return "负数"; }}
希望切入的日志操作:
package com.invicme.apps.aop.proxy;import java.lang.reflect.Method;import java.util.Arrays;import org.apache.log4j.Logger;import org.springframework.aop.AfterReturningAdvice;import org.springframework.aop.MethodBeforeAdvice;/** * * @author lucl * */public class LogAdapter implements MethodBeforeAdvice, AfterReturningAdvice { private static final Logger logger = Logger.getLogger(LogAdapter.class); /** * 目标对象的方法执行之前打印日志 */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { logger.info(target.getClass().getName() + "@" + method.getName() + " with args " + Arrays.asList(args) + " invoke."); } /** * 目标对象的方法执行之后打印日志 */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { logger.info(target.getClass().getName() + "@" + method.getName() + " ends with result " + returnValue + " ."); } }
Spring配置文件:
.*add .*iv .validate*
Junit单元测试类:
package com.test.apps.spring.aop;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.invicme.apps.aop.proxy.ArithmeticCalculate;import com.invicme.apps.aop.proxy.DivisorIsZeroException;/** * * @author lucl * */public class TestSpringProxyFactoryBean { @Test public void testProxyFactoryBean() { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-context-aop.xml"); ArithmeticCalculate calculate = context.getBean("proxyFactoryBean", ArithmeticCalculate.class); calculate.add(1, 2); System.out.println("----------------------------------------------------------------"); try { calculate.div(12, 3); } catch (DivisorIsZeroException e) { e.printStackTrace(); } }}
输出结果:
说明:
ProxyFactoryBean是一个代理,我们可以把它转换为proxyInterfaces中指定的实现该interface的代理对象。
通过代理模式可以获取到业务日志中希望输出的日志内容。
OK!这是我们想要的结果,但是上面这个过程貌似有点复杂,尤其是配置切点跟通知,Spring提供了一种自动代理的功能,能让切点跟通知自动进行匹配,修改配置文件如下:
.*add .*iv .validate*
执行程序:
package com.test.apps.spring.aop;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.invicme.apps.aop.proxy.ArithmeticCalculate;import com.invicme.apps.aop.proxy.DivisorIsZeroException;/** * * @author lucl * */public class TestSpringProxyFactoryBean { @Test public void testProxyFactoryBean() { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-context-aop.xml"); /*ArithmeticCalculate calculate = context.getBean("proxyFactoryBean", ArithmeticCalculate.class);*/ ArithmeticCalculate calculate = context.getBean("calculate", ArithmeticCalculate.class); calculate.add(1, 2); System.out.println("----------------------------------------------------------------"); try { calculate.div(12, 3); } catch (DivisorIsZeroException e) { e.printStackTrace(); } }}
说明:org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator能够为方法匹配的bean自动创建代理!