为什么使用AOP?
1:代码混乱:越来越多的非业务需求(比如日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理自己的业务逻辑的同时还要兼顾其它多个关注点。
2:代码分散:假如就单单的满足加入日志需求,就不得不在多个模块中重复相同的日志代码,如果日志需求发送了改变,还要去修改所有的模块。
解决方案:
现在有一个业务类:
package com.shiro.springbootshiro.aop; /** * 作用:spring aop 中的业务类 */ public interface ArithmeticCalculator { int add(int i,int j); }
实现这个业务类:
package com.shiro.springbootshiro.aop; /** * 作用:实现这个业务类 */ public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { return i+j; } }
给这个业务加上日志信息:使用动态代理
package com.shiro.springbootshiro.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * 作用:给spring aop中业务添加日志需求,使用动态代理 */ public class ArithmeticCalculatorLoggingProxy { //要代理的对象 private ArithmeticCalculator target; public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) { this.target = target; } public ArithmeticCalculator getLoggingProxy(){ ArithmeticCalculator proxy = null; //代理对象由哪一个类加载器负责加载。 ClassLoader loader = target.getClass().getClassLoader(); //代理对象的类型,即其中有哪些方法。 Class[] interfaces = new Class[]{ArithmeticCalculator.class}; //当调用代理对象中的方法的时候,要先执行什么代码。 InvocationHandler h = new InvocationHandler() { /** * * @param proxy : 正在返回的那个代理对象,一般情况下,在invoke都不使用该对象。 * @param method : 正在调用的那个方法 * @param args :调用方法时,传入的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); //在执行方法前,先打印出方法的名称 System.out.println("先打印出方法的名字:"+name+"开始:"+ Arrays.asList(args)); //执行方法 Object result = method.invoke(target, args); //执行方法后,打印日志,输出结果 System.out.println("打印"+name+"后的结果:"+result); return result; } }; proxy= (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h); return proxy; } }
测试:
package com.shiro.springbootshiro.aop; /** * 作用:实现spring aop中的业务代码 */ public class Main { public static void main(String[] args) { //目标方法 ArithmeticCalculator target = new ArithmeticCalculatorImpl(); //代理类 ArithmeticCalculator proxy= new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy(); int i = proxy.add(3, 4); System.out.println(i); } }
输出:
使用AOP
aop是面向切面编程。切面模块化横切关注点就是要添加的日志功能。
好处:每个业务的逻辑不用修改。便于维护和升级。业务模块更加的简洁。
验证是一个切面,日志是一个切面。
概念:
切面(Aspect):横切关注点。就是要添加的功能验证,日志。
通知(Advice):切面必须要完成的工作。
目标(Target):被通知的对象,就是原有的业务。
代理(Proxy):向目标对象应用通知之后创建的对象。
连接点(Joinpoint):程序执行的某个特点位置。比较类的某个方法执行前,或者执行后。比如上面的add方法,就是连接点。
切点(pointcut):每个类拥有多个连接点。AOP通过特点的切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点就是查询条件。一个切点可以匹配多个连接点。使用的是类和方法作为连接点的查询条件。
在springboot中使用AOP
1:加入依赖:
<!--使用spring的AOP--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2:写切面类
package com.example.aop.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * 作用: 测试spring 的AOP */ @Aspect //说明这是一个切面类 @Component // 添加到IOC容器中 public class UserServiceAspect { private static final Logger log = LoggerFactory.getLogger(UserServiceAspect.class); /*@PointCut注解表示表示横切点,哪些方法需要被横切*/ /*切点表达式*/ @Pointcut("execution(public * com.example.aop.service.*.*(..))") /*切点签名*/ public void print() { } @Before("print()") public void before(JoinPoint joinPoint){ log.info("前置切面before……"); Object[] args = joinPoint.getArgs(); String methodName = joinPoint.getSignature().getName(); System.out.println("参数:"+args); System.out.println("方法名:"+methodName); } }