接上一遍aop入门学习1继续往下
通过上一节,我们已经准备好了写aop的基本前提工作,接下去就开始学习如果写aop
什么是AOP?
面向切面编程,Aspect Oriented Programming的缩写,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术,AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
官方解释可能不太好理解,这里我根据自己理解总结为:
在类和类之间进行方法调用时,能用一种技术添加一种拦截机制,即AOP。特点:能拦截一个类中的方法,也可以拦截很多类中的很多方法。
搞懂3个问题即可学会AOP 。
1、谁来拦截?
需要一个切面类,类里面写拦截的代码
如图:新增一个切面类
类中新增@Component @Aspect注解,同时在配置文件中新增包扫描路劲
<context:component-scan base-package="com.wys.service.impl,com.wys.aop"></context:component-scan>
2、拦截谁?怎么拦?
package com.wys.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Author:花季岁月
* @Date Created in 2022/3/28 13:03
* @Description:切面类 进行拦截
* 1、想要在spring容器内 实现AOP功能
*/
@Component
@Aspect
public class AspectDemo {
//2、拦截谁(在注解@Pointcut中添加参数来确定拦截谁,这里做测试,拦截的是saleSpirit()这个方法,在注解中添加如下参数即可"execution(void com.wys.service.impl.ShopServiceImpl.saleSpirit(Integer))"),怎么拦截
//切入点
//注意:a、参数里面要有返回值 以及方法对应的参数 b、因为@Pointcut注解需要加在方法上,所以要有一个空方法,方法名随意
@Pointcut("execution(void com.wys.service.impl.ShopServiceImpl.saleSpirit(Integer))")
public void aspectTest(){}
}
代码示例中只拦截一个方法,若想拦截多个方法,该如何做呢?
这里可以使用通配符来配置
(1)拦截包内所有类 参数类型是Integer,返回值类型是void
//扫描该类中的所有方法 参数类型必须是一个,且返回类型是void的方法
@Pointcut("execution(void com.wys.service.impl.ShopServiceImpl.*(Integer))")
(2)拦截包内所有类,类中所有方法,参数个数和类型忽略不计,返回类型任意
//使用..表示所有参数类型,用*表示所有返回值类型
@Pointcut("execution(* com.wys.service.impl.ShopServiceImpl.*.*(..))")
3、拦截完干什么?
首先看一张图,了解拦截的位置:
类A调用类B方法时,通过新增切面类进行对B中的方法(案例仅针对其中一个方法进行拦截)进行拦截。
案例:对啤酒方法进行拦截(环绕通知)
package com.wys.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Author:花季岁月
* @Date Created in 2022/3/28 13:03
* @Description:切面类 进行拦截
* 1、想要在spring容器内 实现AOP功能
*/
@Component
@Aspect
public class AspectDemo {
//2、拦截谁(在注解@Pointcut中添加参数来确定拦截谁,这里做测试,拦截的是saleBeer()这个方法,在注解中添加如下参数即可"execution(void com.wys.service.impl.ShopServiceImpl.saleBeer(Integer))"),怎么拦截
//切入点
//注意:a、参数里面要有返回值 以及方法对应的参数 b、因为@Pointcut注解需要加在方法上,所以要有一个空方法,方法名随意
@Pointcut("execution(void com.wys.service.impl.ShopServiceImpl.saleBeer(Integer))")
public void aspectTest(){}
//3、拦截完干什么?
//环绕通知
@Around("aspectTest()")
public void aspect(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("我来拦截了,拦截的是啤酒的方法,你将看不到啤酒的价格了!");
}
}
如代码所示:新增拦截方法,并新增环绕注解@Around("aspectTest()") 参数为代码第2步中切入点的方法。这里要注意,此时这样是拦截不了的,还得在配置文件中开启AOP注解,如果不开启,切面类中的@Aspect、@Pointcut、@Around注解将不会生效。
<!-- 开启AOP注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
启动main方法测试:
运行结果可以看到,此时 saleBeer()方法已经被拦截到了,看不到啤酒的价格了。
接下去,再看看,我们拦截完还能干什么?
由上面可以看到,在环绕通知单方法中,带了一个参数ProceedingJoinPoint proceedingJoinPoint
通过这个参数,可以:
(1)获取拦截到的类名: proceedingJoinPoint.getTarget().getClass().getName();
(2)获取拦截到的方法:proceedingJoinPoint.getSignature().getName();
(3)获取拦截到的方法中的参数值:proceedingJoinPoint.getArgs();
除了这些,还有其他的这里就不一一列举了
package com.wys.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.stereotype.Component;
import java.util.Arrays;
/**
* @Author:花季岁月
* @Date Created in 2022/3/28 13:03
* @Description:切面类 进行拦截
* 1、想要在spring容器内 实现AOP功能
*/
@Component
@Aspect
public class AspectDemo {
//2、拦截谁(在注解@Pointcut中添加参数来确定拦截谁,这里做测试,拦截的是saleBeer()这个方法,在注解中添加如下参数即可"execution(void com.wys.service.impl.ShopServiceImpl.saleBeer(Integer))"),怎么拦截
//切入点
//注意:a、参数里面要有返回值 以及方法对应的参数 b、因为@Pointcut注解需要加在方法上,所以要有一个空方法,方法名随意
@Pointcut("execution(void com.wys.service.impl.ShopServiceImpl.saleBeer(Integer))")
public void aspectTest() {
}
//3、拦截完干什么?
//环绕通知
@Around("aspectTest()")
public Object aspect(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("我来拦截了,拦截的是啤酒的方法,你将看不到啤酒的价格了!");
//拦截的类
String className = proceedingJoinPoint.getTarget().getClass().getName();
System.out.println("被拦截的类:" + className);
//拦截的方法
String methodName = proceedingJoinPoint.getSignature().getName();
System.out.println("被拦截的方法:" + methodName);
//拦截的参数值
Object[] args = proceedingJoinPoint.getArgs();
System.out.println("被拦截的参数:" + Arrays.toString(args));
Object proceed = null;
try {
//执行连接点 执行被拦截的方法
proceed = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
最重要的一个,可以通过proceedingJoinPoint.proceed()来执行被拦截的方法,前面若没执行这个方法,我们可以看到结果是不打印‘啤酒的价格了’,也就是被拦截了不执行了,那么加了这个执行连接点的方法,会发现又能重新打印啤酒的价格了,说明这个就是用来重新执行被拦截的方法。
注意下:这里执行proceedingJoinPoint.proceed()后有返回值,所以需要在方法上将原来返回值void改为Object
测试修改返回值:这里的目的其实就是如果有某些场景,需要在拦截的时候将原来返回值改掉,那么就可以使用带参的返回方法。
总结下:为什么说环绕通知包含前置通知、后置通知、异常通知、最终执行,后置跟异常只会执行其中一个
通俗的解释什么叫环绕通知?
环绕通知可以对连接点是否执行进行控制,环绕通知包含前置、后置、异常和最终。
5种通知:
(1)前置通知:@Before 一定会执行
(2)(3)不一定谁执行
(2)后置通知:@AfterReturning
(3)异常通知:@AfterThrowing
(4)最终通知:@After 一定会执行
(5)环绕通知:@Around
现在把其他4种通知也写一下:
/**
* 前置通知
* @param joinPoint
*/
@Before("aspectTest()")
public void beforeAspect(JoinPoint joinPoint){
System.out.println("前置通知!");
}
/**
* 后置通知
* @param joinPoint
*/
@AfterReturning("aspectTest()")
public void afterReturningAspect(JoinPoint joinPoint){
System.out.println("后置通知!");
}
/**
* 异常通知
* @param joinPoint
*/
@AfterThrowing("aspectTest()")
public void afterThrowingAspect(JoinPoint joinPoint){
System.out.println("异常通知!");
}
/**
* 最终通知
* @param joinPoint
*/
@After("aspectTest()")
public void afterAspect(JoinPoint joinPoint){
System.out.println("最终通知!");
}
总结:Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议使用JDK动态代理)
被代理的目标对象实现了至少一个接口,则会使用JDK动态代理,所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。