先用代码讲一下什么是传统的AOP(面向切面编程)编程
需求:实现一个简单的计算器,在每一步的运算前添加日志。最传统的方式如下:
Calculator.java
package cn.limbo.spring.aop.calculator; /** * Created by Limbo on 16/7/14. */ public interface Calculator { int add(int i , int j); int sub(int i , int j); int mul(int i , int j); int div(int i , int j); }
CalculatorImpl.java
package cn.limbo.spring.aop.calculator; /** * Created by Limbo on 16/7/14. */ public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { System.out.println("The method add begin with [ "+ i +"," + j+" ]"); System.out.println("The method add end with [ "+ i +"," + j+"]"); return i + j; } @Override public int sub(int i, int j) { System.out.println("The method sub begin with [ "+ i +"," + j+" ]"); System.out.println("The method sub end with [ "+ i +"," + j+" ]"); return i - j; } @Override public int mul(int i, int j) { System.out.println("The method mul begin with [ "+ i +"," + j+" ]"); System.out.println("The method mul end with [ "+ i +"," + j+" ]"); return i * j; } @Override public int div(int i, int j) { System.out.println("The method div begin with [ "+ i +"," + j+" ]"); System.out.println("The method div end with [ "+ i +"," + j+" ]"); return i / j; } }
这样就完成了需求,但是我们发现,倘若是要修改日志的信息,那么就需要在具体方法里面改,这样做很麻烦,而且把原本清爽的方法改的十分混乱,方法应该表现的是核心功能,而不是这些无关紧要的关注点,下面用原生的java的方式实现在执行方法时,动态添加输出日志方法
CalculatorImpl.java
package cn.limbo.spring.aop.calculator; /** * Created by Limbo on 16/7/14. */ public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { return i + j; } @Override public int sub(int i, int j) { return i - j; } @Override public int mul(int i, int j) { return i * j; } @Override public int div(int i, int j) { return i / j; } }
CalculatorLoggingProxy.java
package cn.limbo.spring.aop.calculator; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Objects; /** * Created by Limbo on 16/7/14. */ public class CalculatorLoggingProxy { //要代理的对象 private Calculator target; public CalculatorLoggingProxy(Calculator target) { this.target = target; } public Calculator getLoggingProxy(){ //代理对象由哪一个类加载器负责加载 ClassLoader loader = target.getClass().getClassLoader(); //代理对象的类型,即其中有哪些方法 Class[] interfaces = new Class[]{Calculator.class}; // 当调用代理对象其中的方法时,执行改代码 InvocationHandler handler = new InvocationHandler() { @Override /** * proxy:正在返回的那个对象的代理,一般情况下,在invoke方法中不使用该对象 * method:正在被调用的方法 * args:调用方法时,传入的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); //日志 Object result = method.invoke(target,args); System.out.println("The method " + methodName + " ends with " + result); return result; } }; Calculator proxy = (Calculator) Proxy.newProxyInstance(loader,interfaces,handler); return proxy; } }
Main.java
package cn.limbo.spring.aop.calculator; /** * Created by Limbo on 16/7/14. */ public class Main { public static void main(String[] args) { Calculator calculator = new CalculatorImpl(); Calculator proxy = new CalculatorLoggingProxy(calculator).getLoggingProxy(); int result = proxy.add(1,2); System.out.println("--->" + result); result = proxy.sub(1,2); System.out.println("--->" + result); result = proxy.mul(3,2); System.out.println("--->" + result); result = proxy.div(14,2); System.out.println("--->" + result); } }
这样写虽然已经简化了代码,而且可以任意修改日志信息代码,但是写起来还是很麻烦!!!
下面我们使用spring自带的aop包实现但是要加入
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.8.5.RELEASE.jar
这两个额外的包
下面看代码,要点全部卸载代码里面了
Calculator.java
package cn.limbo.spring.aop.impl; /** * Created by Limbo on 16/7/14. */ public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
CalculatorImpl.java
package cn.limbo.spring.aop.impl; import org.springframework.stereotype.Component; /** * Created by Limbo on 16/7/14. */ @Component("calculatorImpl") public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { return i + j; } @Override public int sub(int i, int j) { return i - j; } @Override public int mul(int i, int j) { return i * j; } @Override public int div(int i, int j) { return i / j; } }
LoggingAspect.java
package cn.limbo.spring.aop.impl; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; import java.util.Objects; /** * Created by Limbo on 16/7/14. */ //把这个类声明为切面:需要该类放入IOC容器中,再声明为一个切面 @Order(0)//指定切面优先级,只越小优先级越高 @Aspect @Component public class LoggingAspect { /** * 定义一个方法,用于声明切入点表达式,一般的,该方法中再不需要添加其他代码 * 主要是为了重用路径,使用@Pointcut来声明切入点表达式 * 后面的其他通知直接使用方法名来引用当前的切入点表达式 */ @Pointcut("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))") public void declareJointPointExpression() { } //声明该方法是一个前置通知:在目标方法之前执行 @Before("declareJointPointExpression()") // @Before("execution(* cn.limbo.spring.aop.impl.*.*(..))") 该包下任意返回值,任意类,任意方法,任意参数类型 public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The Method "+ methodName+" Begins With " + args); } //在目标方法执行之后执行,无论这个方法是否出错 //在后置通知中还不能访问目标方法的返回值,只能通过返回通知访问 @After("execution(* cn.limbo.spring.aop.impl.Calculator.*(int,int))") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("The Method "+ methodName+" Ends "); } /** * 在方法正常结束后执行的代码 * 返回通知是可以访问到方法的返回值 */ @AfterReturning(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",returning = "result") public void afterReturning(JoinPoint joinPoint , Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("The Method " + methodName + " Ends With " + result); } /** *在目标方法出现异常的时候执行代码 * 可以访问异常对象,且可以指定出现特定异常时再执行通知 */ @AfterThrowing(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex)//Exception 可以改成 NullPointerException等特定异常 { String methodName = joinPoint.getSignature().getName(); System.out.println("The Method " + methodName + " Occurs With " + ex); } /** * 环绕通知需要ProceedingJoinPoint 类型参数 功能最强大 * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法 * 且环绕通知必须有返回值,返回值即为目标方法的返回值 */ @Around("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))") public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) { Object result =null; String methodName = proceedingJoinPoint.getSignature().getName(); try { //前置通知 System.out.println("The Method " + methodName + " Begins With " + Arrays.asList(proceedingJoinPoint.getArgs())); result = proceedingJoinPoint.proceed(); // 返回通知 System.out.println("The Method " + methodName + " Ends With " + result); } catch (Throwable throwable) { //异常通知 throwable.printStackTrace(); } System.out.println("The Method " + methodName + " Ends "); return result; } }
applicationContext.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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置自动扫描的包--> <context:component-scan base-package="cn.limbo.spring.aop.impl"></context:component-scan> <!--使Aspect注解起作用,自动为匹配的类生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
用xml来配置aop
application-config.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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="userManager" class="com.tgb.aop.UserManagerImpl"/> <!--<bean id="aspcejHandler" class="com.tgb.aop.AspceJAdvice"/>--> <bean id="xmlHandler" class="com.tgb.aop.XMLAdvice" /> <aop:config> <aop:aspect id="aspect" ref="xmlHandler"> <aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/> <aop:before method="doBefore" pointcut-ref="pointUserMgr"/> <aop:after method="doAfter" pointcut-ref="pointUserMgr"/> <aop:around method="doAround" pointcut-ref="pointUserMgr"/> <aop:after-returning method="doReturn" pointcut-ref="pointUserMgr"/> <aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/> </aop:aspect> </aop:config> </beans>
一共有5类的通知,其中around最为强大,但是不一定最常用,aspect的底层实现都是通过代理来实现的,只能说这个轮子造的不错
时间: 2024-10-25 17:09:38