个人博客网:https://wushaopei.github.io/ (你想要这里多有)
一、AOP切面编程
1、什么是AOP
AOP是面向切面编程。全称:Aspect Oriented Programming
面向切面编程指的是:程序是运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。
2、一个简单计算数功能加日记
测试:
结果:
方法 前置通知logBefore 是【add】,参数是:[100, 100]
目标方法 add(int num1, int num2) 正在执行
方法 后置通知logAfter 是【add】,参数是:[100, 100]
方法 返回通知logAfterReturning 是【add】,返回值是:200
=================================
方法 前置通知logBefore 是【div】,参数是:[100, 0]
目标方法 div(int num1, int num2) 正在执行
方法 后置通知logAfter 是【div】,参数是:[100, 0]
方法 异常通知logAfterThrowing 是【div】,异常是:java.lang.ArithmeticException: / by zero
十月 29, 2019 8:01:20 下午 org.springframework.context.support.GenericApplicationContext doClose
信息: Closing org.springframework.context.support.GenericApplicationContext@42d3bd8b: startup date [Tue Oct 29 20:01:19 CST 2019]; root of context hierarchy
实现业务原理分析:
4、使用代理实现日记
4.1、使用jdk动态代理统一日记
增强功能:
代理类:
测试及结果:
方法 前置通知logBefore 是【add】,参数是:[100, 200]
目标方法 add(int num1, int num2) 正在执行
方法 后置通知logAfter 是【add】,参数是:[100, 200]
方法 返回通知logAfterReturning 是【add】,返回值是:300
300
=============华丽的分隔线=================
方法 前置通知logBefore 是【div】,参数是:[200, 0]
目标方法 div(int num1, int num2) 正在执行
方法 后置通知logAfter 是【div】,参数是:[200, 0]
方法 异常通知logAfterThrowing 是【div】,异常是:java.lang.reflect.InvocationTargetException
优点:这种方式已经解决我们前面所有日记需要的问题。非常的灵活。而且可以方便的在后期进行维护和升级。
缺点:当然使用jdk动态代理,需要有接口。如果没有接口。就无法使用jdk动态代理。
4.2、使用cglib代理
执行结果:
方法 前置通知logBefore 是【add】,参数是:[100, 200]
目标方法 add(int num1, int num2) 正在执行
方法 后置通知logAfter 是【add】,参数是:[100, 200]
方法 返回通知logAfterReturning 是【add】,返回值是:300
300
=============华丽的分隔线=================
方法 前置通知logBefore 是【div】,参数是:[200, 0]
目标方法 div(int num1, int num2) 正在执行
方法 后置通知logAfter 是【div】,参数是:[200, 0]
方法 异常通知logAfterThrowing 是【div】,异常是:java.lang.ArithmeticException: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.atguigu.pojo.Calculator.div(Calculator.java:23)
at com.atguigu.pojo.Calculator$$EnhancerByCGLIB$$cb948d28.CGLIB$div$2(<generated>)
at com.atguigu.pojo.Calculator$$EnhancerByCGLIB$$cb948d28$$FastClassByCGLIB$$557a5f0b.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:215)
at com.atguigu.proxy.CglibProxyFactory$1.intercept(CglibProxyFactory.java:52)
at com.atguigu.pojo.Calculator$$EnhancerByCGLIB$$cb948d28.div(<generated>)
at com.atguigu.proxy.CglibProxyFactory.main(CglibProxyFactory.java:25)
优点:在没有接口的情况下,同样可以实现代理的效果。
缺点:同样需要自己编码实现代理全部过程。
但是为了更好的整合Spring框架使用。所以我们需要学习一下Spring 的AOP 功能。也就是学习Spring提供的AOP功能。
二、Spring框架的AOP功能
1、AOP编程的专业术语
通知(Advice)
通知就是增强的代码。比如前置增强的代码。后置增强的代码。异常增强代码。这些就叫通知
切面(Aspect)
切面就是包含有通知代码的类叫切面。
横切关注点
横切关注点,就是我们可以添加增强代码的位置。比如前置位置,后置位置,异常位置。和返回值位置。这些都叫横切关注点。
目标(Target)
目标对象就是被关注的对象。或者被代理的对象。
代理(Proxy)
为了拦截目标对象方法,而被创建出来的那个对象,就叫做代理对象。
连接点(Joinpoint)
连接点指的是横切关注点和程序代码的连接,叫连接点。
切入点(pointcut)
切入点指的是用户真正处理的连接点,叫切入点。
在Spring中切入点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
图解AOP专业术语:
2、使用Spring实现AOP简单切面编程
导包:
切入点表达式,是告诉Spring窗口,当前这个通知对哪个方法感兴趣
前置通知怎么描述@Before
代码
配置信息:
测试代码:
3、Spring切面中的代理对象
在Spring中,可以对有接口的对象和无接口的对象分别进行代理。在使用上有些细微的差别。
1) 如果被代理的对象实现了接口。在获取对象的时候,必须要以接口来接收返回的对象。
2) 如果被代理对象,如果没有实现接口。获取对象的时候使用对象类型本身
4、Spring的切入点表达式
@PointCut切入点表达式语法格式是: execution(访问权限 返回值类型 方法全限定名(参数类型列表))
execution( public int com.webcode.pojo.Calculator.add(int, int) )
访问权限 public
返回值类型 int
方法全限定名 包名+类包+方法名 ===>>>>com.webcode.pojo.Calculator.add
(参数类型列表) (int, int)
限定符:
*表示任意的意思:
1)匹配某全类名下,任意或多个方法。
execution( public int com.webcode.pojo.Calculator.*(int, int) )
对Calculator类的全部方法都匹配(参数类型必须是两个int)
2)在Spring中只有public权限能拦截到,访问权限可以省略(访问权限不能写*)。
execution( int com.webcode.pojo.Calculator.*(int,int))execution( int com.webcode.pojo.Calculator.*(int,int))public 可以省略
execution( int com.webcode.pojo.Calculator.add(int, int) )
3)匹配任意类型的返回值,可以使用 * 表示
execution( * com.webcode.pojo.Calculator.add(int, int) )
* 表示任意类型的返回值。
4)匹配任意一层子包。
execution( int com.webcode.*.Calculator.add(int, int) )
上面这个*表示
com.webcode.所有子包,都可以匹配
5)任意类型参数
execution( int com.webcode.pojo.Calculator.add(int, *) )
上面这个*表示任意参数类型
..:可以匹配多层路径,或任意多个任意类型参数
1)任意层级的包
execution( int com.webcode..Calculator.add(int, int) )
上面的..表示 com.webcode下的所有包都匹配
2)任意个任意类型的参数
execution( int com.webcode..Calculator.add(int, ..) )
上面的..表示任意个,任意类型的参数
模糊匹配:
// 表示任意返回值,任意方法全限定符,任意参数
execution(* *(..))
// 表示任意返回值,任意包名+任意方法名,任意参数
execution(* *.*(..))
精确匹配:
execution(public int com.webcode.pojo.Calculator.add(int, int) )
确定返回值必须为:int
包名必须是:com.webcode.pojo
类包必须是:Calculator
方法名必须是:add
参数类型必须是两个int (int, int)
切入点表达式连接:&& 、||
// 表示需要同时满足两个表达式
@Before("execution(public int com.webcode.aop.Calculator.add(int, int))"
+ " && "
+ "execution(public * com.webcode.aop.Calculator.add(..))")
// 表示两个条件只需要满足一个,就会被匹配到
@Before("execution(public int com.webcode.aop.Calculator.add(int, int))"
+ " || "
+ "execution(public * com.webcode.aop.Calculator.a*(int))")
后面只对Service使用。
execution(public * com.webcode.service..*Service*.*(..))
5、Spring通知的执行顺序
Spring通知的执行顺序是:
正常情况:
前置通知====>>>>目标方法====>>>>后置通知=====>>>>返回值之后
异常情况:
前置通知====>>>>目标方法====>>>>后置通知=====>>>>抛异常通知
正常情况:
异常情况:
6、获取连接点信息
JoinPoint 是连接点的信息。
只需要在通知方法的参数中,加入一个JoinPoint参数。就可以获取到拦截方法的信息。
注意:是org.aspectj.lang.JoinPoint这个类。
打印结果:
一个是正常的,一个是发生异常的
7、获取拦截方法的返回值和抛的异常信息
获取方法返回的值分为两个步骤:
1、在返回值通知的方法中,追加一个参数 Object result
2、然后在@AfterReturning注解中添加参数returning="参数名"
获取方法抛出的异常分为两个步骤:
- 在异常通知的方法中,追加一个参数Throwable exception
- 然后在@AfterThrowing 注解中添加参数 throwing="参数名"
图解方法抛异常:
8、Spring的环绕通知
- 环绕通知使用@Around注解。
- 环绕通知如果和其他通知同时执行。环绕通知会优先于其他通知之前执行。
- 环绕通知一定要有返回值(环绕如果没有返回值。后面的其他通知就无法接收到目标方法执行的结果)。
- 在环绕通知中。如果拦截异常。一定要往外抛。否则其他的异常通知是无法捕获到异常的。
结果打印与分析:
9、切入点表达式的复用
- 定义一个空方法,在方法上使用@Pointcut定义切入点表达式
2、在需要使用切入点表达式的地方。使用 “方法名()” 代替 切入点表达式
10、多个切面的执行顺序
当我们有多个切面,多个通知的时候:
- 通知的执行顺序默认是由切面类的字母先后顺序决定。
- 在切面类上使用@Order注解决定通知执行的顺序(值越小,越先执行)
Order的值,越小越优先。
也就是上面两个切面类,LogUtil比A先执行。
执行结果
11、如何基于xml配置aop程序
1、拷贝前面注解形式的aop工程
2、去掉前面使用的所有注解
applicationContext.xml配置文件内容: