博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringAOP_02 AOP的使用
阅读量:3967 次
发布时间:2019-05-24

本文共 11616 字,大约阅读时间需要 38 分钟。


本人是个新手,写下博客用于自我复习、自我总结。

如有错误之处,请各位大佬指出。
学习资料来源于:尚硅谷


Calculator.java:

package com.guigu.inter;public interface Calculator {
public int add(int i,int j); public int sub(int i,int j); public int mul(int i,int j); public int div(int i,int j); }

MyMathCalculator.java:

package com.guigu.impl;import org.springframework.stereotype.Service;//import com.guigu.inter.Calculator;@Service//public class MyMathCalculator implements Calculatorpublic class MyMathCalculator{
public int add(int i, int j) {
System.out.println("方法内部执行"); int result = i + j ; return result; } public int sub(int i, int j) {
System.out.println("方法内部执行"); int result = i - j ; return result; } public int mul(int i, int j) {
System.out.println("方法内部执行"); int result = i * j ; return result; } public int div(int i, int j) {
System.out.println("方法内部执行"); int result = i / j ; return result; } }

applicationContext.xml:

AOPTest.java:

package com.guigu.test;import static org.junit.Assert.*;import org.aspectj.lang.annotation.Before;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.guigu.impl.MyMathCalculator;import com.guigu.inter.Calculator;import com.guigu.utils.LogUtils;public class AOPTest {
/** * 从目前来看,我们的目标是:如何将LogUtils类(切面类)中的这些方法(通知方法) * 动态地在目标方法运行的各个位置切入。 * * AOP的使用步骤: * 1)导包;(以后把这些都导进来) * Spring中支持面向切面编程的包:aspect (基础版的) * 加强版面向切面编程的包(即使目标对象没有实现任何接口也能创建动态代理): * com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar * 2)写配置; * ①将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到ioc容器中 * : * @Controller:控制器:我们推荐给控制器层(servlet包下)的组件加这个注解 @Service:业务逻辑:我们推荐给业务逻辑层(service包下)的组件加这个注解 @Repository:给数据库层(持久化层,dao层)的组件添加这个注解 @Component:给不属于以上三层的组件加这个注解 (也就是说不要忘记给这些类加一个注解,以便加入到ioc容器中) (加了注解之后,不要忘记在applicationContext中,对这些注解进行扫描,以便 加入到ioc容器中(不要忘记在namespace中打开context)) * ②还应该告诉Spring到底哪个是切面类 (用注解:@Aspect) * ③告诉Spring,切面类里面的每一个方法,都是何时何地运行的 * :这几个注解叫做通知注解,共有5个 * @Before:在目标方法之前 (前置通知) * @After:在目标方法结束之后 (后置通知) * @AfterReturning:在目标方法正常返回之后 (返回通知) * @AfterThrowing:在目标方法抛出异常之后 (异常通知) * * @Around:环绕 (环绕通知) * * 根据SpringAOP_01中的proxy文件,可以得到如下结论:(注解应该何时加) * try { @Before result = method.invoke(calculator,args); @AfterReturning } catch (Exception e) { @AfterThrowing }finally{ @After } * * 还需要写切入表达式,以便告诉Spring,每个方法的切入点 * 格式:execution(访问权限符 返回值类型 方法全类名) * 如:@Before("execution(public int com.guigu.impl.MyMathCalculator.*(int, int))") * ④开启基于注解的AOP模式 * (详情见applicationContext.xml) * * 3)测试;如下 * */ ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test() {
//1、从ioc容器中拿到目标对象。注意:如果想要用类型,一定用它的接口类型,不要用它本类 /**细节1: * 输出结果: * com.guigu.impl.MyMathCalculator@1a3f495 class $Proxy12 * AOP的底层就是动态代理,容器中保存的组件是它的代理对象:$Proxy12 * 所以这就是为什么一定要用接口类型,因为在SpringAOP_01中可以知道 * 要完成动态代理,就需要它是接口类型。 * * 注意:接口最好不加在容器中(没必要),但实际上也可以加, * 加了也不创建对象,只要一个这个组件是一个接口,相当于告诉Spring, * ioc容器中可能有这种类型的组件。 * */ //Calculator bean = ioc.getBean(Calculator.class); //bean.add(2, 1); //System.out.println(bean); //System.out.println(bean.getClass()); //2、通过id拿到目标对象(id默认是类名首字母小写) //Calculator bean2=(Calculator)ioc.getBean("myMathCalculator"); //System.out.println(bean2.getClass()); //3、如果MyMathCalculator没有实现接口:(用本类类型) /** * 现在没有实现接口也能完成动态代理: * 这是因为cglib帮我们创建好的代理对象 * */ MyMathCalculator bean3 = ioc.getBean(MyMathCalculator.class); //或者:MyMathCalculator bean3 = (MyMathCalculator)ioc.getBean("myMathCalculator"); bean3.add(3,5); System.out.println(bean3.getClass()); } /** * 细节2:切入点表达式的写法 * 固定格式不变:execution(访问权限符 返回值类型 方法全类名) * * 通配符: * * * ①匹配一个或者多个字符 * 如:execution(public int com.guigu.impl.MyMath*r.*(int, int)) * ②匹配任意一个参数(因为有可能对方法进行了重载) * (即第一个参数int类型,第二个参数任意类型) * 如:execution(public int com.guigu.impl.MyMath*r.*(int, *)) * ③只能匹配一层路径 * 如:execution(public int com.guigu.*.MyMath*r.*(int, *)) * ④权限位置*不能用,如果想代表所有权限,直接不写就行 * 也就是说 public可以写,也可以不写 * ⑤返回值位置可以用*,代表任意返回值类型 * .. * ①匹配任意多个参数,任意类型参数 * 如:execution(public int com.guigu.impl.MyMath*r.*(..)) * ②可以匹配多层路径 * 如:execution(public int com.guigu..MyMath*r.*(int, *)) * * 总结来说,2种: * 最模糊的: execution(* *.*(..)) * 第一个*:任意返回值类型 * 第二个*:任意包,任意类 * 第三个*:任意方法 * (显然最模糊的 不能用) * * 最精确的: * execution(public int com.guigu.impl.MyMathCalculator.add(int, int)) * * 其他用法:"&&" "||" "!" * * &&:我们要切入的位置满足这两个表达式 * 如:execution(public int com.guigu..MyMath*.*(..))&& execution(* *.*(int,int)) * 这样一来:MyMathCalculator.add(int,double) 就不能使用 * * ||:我们要切入的位置满足任意一个表达式 * * !:我们要切入的位置只要不是这个表达式 * */ /** * 细节3:通知方法的执行顺序 * * try { @Before result = method.invoke(calculator,args); @AfterReturning } catch (Exception e) { @AfterThrowing }finally{ @After } 以上为理应顺序,但是实际如下: * 正常执行:@Before => @After => @AfterReturning * 异常执行:@Before => @After => @AfterThrowing * */ @Test public void test02() {
MyMathCalculator bean = ioc.getBean(MyMathCalculator.class); bean.add(3,5); System.out.println("=============="); bean.div(6,0); } /** * 细节4:在通知方法运行的时候,拿到目标方法的详细信息 * * 1)只需要为通知方法的参数列表上写一个参数joinPoint * JoinPoint joinPoint,封装了当前目标方法的详细信息 * 详情见LogUtils * 2)用result来接受返回值 * 详情见LogUtils * 3)用exception来接受异常返回值 * 详情见LogUtils * * 为什么能这么用? * 因为Spring对通知方法的要求不严格 * 比如我可以改:private int logEnd(JoinPoint joinPoint){} * * 但是唯一要求的就是方法的参数列表一定不能乱写。 * 因为通知方法时Spring利用反射调用的,每次方法调用得确定这个方法的参数表的值。 * 参数表上的每一个参数,Spring都得知道是什么。 * JoinPoint:认识 * 也就是说,不知道的参数一定要告诉Spring这是什么 * */ /** * AOP使用场景: * (1)AOP加日志保存到数据库; * (2)AOP做权限验证; * (3)AOP做安全检查; * (4)AOP做事务控制; * */}

LogUtils.java:

package com.guigu.utils;import java.lang.reflect.Method;import java.util.Arrays;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;@Aspect@Component@Order(1)public class LogUtils {
/** * 抽取可重用的切入点表达式: * 1、随便声明一个没有实现的返回void的空方法 * 2、给方法上标注@Pointcut注解 * */ @Pointcut("execution(public int com.guigu..MyMath*r.*(..))") public void MyPoint(){
}; //想在执行目标方法之前运行 @Before("MyPoint()") public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行时使用的参数 Object[] args=joinPoint.getArgs(); //获取到方法签名 Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("LogUtils前置【"+name+"】方法开始执行," + "用的参数列表【"+Arrays.asList(args)+"】"); } //想在目标方法正常执行完成之后运行 @AfterReturning(value="MyPoint()",returning="result") public static void logReturn(JoinPoint joinPoint,Object result){
//获取到方法签名 Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("LogUtils返回【"+name+"】方法执行完成," + "它的计算结果是:"+result); } //想在目标方法出现异常的时候运行 @AfterThrowing(value="MyPoint()",throwing="exception") public static void logException(JoinPoint joinPoint,Exception exception) {
//获取到方法签名 Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("LogUtils异常【"+name+"】方法执行异常," +"它的异常信息是:"+exception +",这个异常已经通知测试小组。"); } //想在目标方法结束的时候运行 @After("MyPoint()") public static void logEnd(JoinPoint joinPoint) {
//获取到方法签名 Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("LogUtils后置【"+name+"】方法最终结束了"); } /** * @Around:环绕 :是Spring中强大的通知;(其实就是动态代理) * * try { @Before result = method.invoke(calculator,args); @AfterReturning } catch (Exception e) { @AfterThrowing }finally{ @After } 以上四种通知合在一起(四合一)就是环绕通知。 它能这么用,就是因为环绕通知中有一个参数ProceedingJoinPoint pjp 而且环绕通知的顺序是正确的@Before => @AfterReturning => @After 也就是说,这两种办法选其中一种就可以实现功能。 如果现在这两种办法一起用(没有注释掉) 那么环绕通知会优先于普通通知执行。执行顺序: 【环绕前置通知】【add方法开始】 LogUtils前置【add】方法开始执行,用的参数列表【[3, 5]】 方法内部执行 【环绕返回通知】【add方法返回,返回值8】 【环绕后置通知】【add方法结束】 LogUtils后置【add】方法最终结束了 LogUtils返回【add】方法执行完成,它的计算结果是:8 * */ @Around("MyPoint()") public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs(); Signature signature=pjp.getSignature(); String name =signature.getName(); Object proceed = null; try{
System.out.println("【环绕前置通知】【"+name+"方法开始】"); //就是利用反射调用目标方法即可,就是method.invoke(obj,args) proceed = pjp.proceed(args); System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】"); }catch(Exception e){
System.out.println("【环绕异常通知】【"+name+"方法异常】,异常信息"+e); //为了让外界知道这个异常,这个异常一定抛出去 throw new RuntimeException(e); }finally{
System.out.println("【环绕后置通知】【"+name+"方法结束】"); } //反射调用后的返回值也一定返回出去 return proceed; }}

ValidateAspect.java:

package com.guigu.utils;import java.util.Arrays;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;@Aspect@Component@Order(2)public class ValidateApsect {
/** * 第二个切面 * 这样一来,不包括环绕通知的话,输出顺序是: LogUtils前置【add】方法开始执行,用的参数列表【[3, 5]】 ValidateApsect前置【add】方法开始执行,用的参数列表【[3, 5]】 方法内部执行 ValidateApsect后置【add】方法最终结束了 ValidateApsect返回【add】方法执行完成,它的计算结果是:8 LogUtils后置【add】方法最终结束了 LogUtils返回【add】方法执行完成,它的计算结果是:8 * *为什么先LogUtils是因为,它是按照首字母顺序排列的。但无论怎么排, *永远都是先执行的后结束,后执行的先结束,如上所示。与环绕的不同。 *除了按照首字母顺序排以外,还有其他办法调整输出顺序。 *就是加上一个标签@Order(1) //使用Order改变切面顺序,数值越小,优先级越高。 * *之后再在LogUtils加上环绕通知后,执行顺序: 【环绕前置通知】【add方法开始】 LogUtils前置【add】方法开始执行,用的参数列表【[3, 5]】 ValidateApsect前置【add】方法开始执行,用的参数列表【[3, 5]】 方法内部执行 ValidateApsect后置【add】方法最终结束了 ValidateApsect返回【add】方法执行完成,它的计算结果是:8 【环绕返回通知】【add方法返回,返回值8】 【环绕后置通知】【add方法结束】 LogUtils后置【add】方法最终结束了 LogUtils返回【add】方法执行完成,它的计算结果是:8 *也就是说,环绕只影响当前切面 * */ @Before("com.guigu.utils.LogUtils.MyPoint()") public void logStart(JoinPoint joinPoint){
Object[] args=joinPoint.getArgs(); Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("ValidateApsect前置【"+name+"】方法开始执行," + "用的参数列表【"+Arrays.asList(args)+"】"); } @AfterReturning(value="com.guigu.utils.LogUtils.MyPoint()",returning="result") public void logReturn(JoinPoint joinPoint,Object result){
Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("ValidateApsect返回【"+name+"】方法执行完成," + "它的计算结果是:"+result); } @AfterThrowing(value="com.guigu.utils.LogUtils.MyPoint()",throwing="exception") public void logException(JoinPoint joinPoint,Exception exception) {
Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("ValidateApsect异常【"+name+"】方法执行异常," +"它的异常信息是:"+exception +",这个异常已经通知测试小组。"); } @After("com.guigu.utils.LogUtils.MyPoint()") public void logEnd(JoinPoint joinPoint) {
Signature signature=joinPoint.getSignature(); String name =signature.getName(); System.out.println("ValidateApsect后置【"+name+"】方法最终结束了"); }}

转载地址:http://gxyki.baihongyu.com/

你可能感兴趣的文章
常见Oracle HINT的用法
查看>>
JAVA中各类CACHE机制实现的比较 [转]
查看>>
PL/SQL Developer技巧
查看>>
3-python之PyCharm如何新建项目
查看>>
15-python之while循环嵌套应用场景
查看>>
17-python之for循环
查看>>
18-python之while循环,for循环与else的配合
查看>>
19-python之字符串简单介绍
查看>>
20-python之切片详细介绍
查看>>
P24-c++类继承-01详细的例子演示继承的好处
查看>>
P8-c++对象和类-01默认构造函数详解
查看>>
P1-c++函数详解-01函数的默认参数
查看>>
P3-c++函数详解-03函数模板详细介绍
查看>>
P4-c++函数详解-04函数重载,函数模板和函数模板重载,编译器选择使用哪个函数版本?
查看>>
P5-c++内存模型和名称空间-01头文件相关
查看>>
P6-c++内存模型和名称空间-02存储连续性、作用域和链接性
查看>>
P9-c++对象和类-02构造函数和析构函数总结
查看>>
P10-c++对象和类-03this指针详细介绍,详细的例子演示
查看>>
Mule ESB-Content-Based Routing Tutorial(1)
查看>>
Mule ESB-Content-Based Routing Tutorial(2)
查看>>