AspectJ 对 AOP 的实现
文章目录
前言
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式
a. AspectJ 中常用的通知有四种类型:
(1)前置通知@Before
(2)后置通知@AfterReturning
(3)环绕通知@Around
(4)最终通知@After
b. AspectJ 的切入点表达式
(1) execution(访问权限 方法返回值 方法声明(参数) 异常类型)
(2)符号定义:
[ * ] : 任意n个字符
[ . . ]: 用在方法参数中,表任意多个参数; 用在包名后,表当前包及其子路径
c. 实现的步骤:
添加依赖
1)创建业务接口
2)创建业务实现
3)创建切面类,实现切面方法
4)在applicationContext.xml文件中进行切面绑定
提示:AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。本文只说注解
一、@Before前置通知实现
1.1创建业务接口
public interface SumService {
String doSome(String name,int age);
String show();
}
1.2创建业务实现
@Service
public class SumServiceImpl implements SumService {
@Override
public String doSome(String name, int age) {
System.out.println("doSome业务功能实现。。。。");
return "aaabbbccc";
}
@Override
public String show() {
System.out.println("show的业务功能实现。。。。");
return "111";
}
}
1.3创建切面类,实现切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component
public class MyAspectJ {
/**
* 所以切面功能都是有切面方法实现的
* 可以将各种切面都在此类中开发
*
* 前置通知的切面方法规范
* 1)访问权限:public
* 2)方法返回值 void
* 3)方法名称自定义
* 4)方法没有参数 有也是joinPoint类型
* 5) 必须使用@Before 注解来声明切入时机是前切功能和切入点
*/
@Before(value = "execution(public String com.bjpowernode.s01.SumServiceImpl.*(..))")
public void myBefore(JoinPoint jp){
/**
*不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
*/
System.out.println("前切方法实现。。。。");
System.out.println("目标方法签名: "+jp.getSignature());
System.out.println("目标方法值: "+ Arrays.toString(jp.getArgs()));
}
}
AspectJ框架切换JDK动态代理和CGLib动态代理
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
===>默认是JDK动态代理,取时必须使用接口类型
<aop:aspectj-autoproxy proxy-target-class=“true”></aop:aspectj-autoproxy>
==>设置为CGLib子类代理,可以使用接口和实现类接
记住:使用接口来接,永远不出错.
1.4 在.xml配置文件里进行切面绑定
<!-- 包扫描 创建对象-->
//包扫描 创建切面类对象 和 目标类对象
<context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>
<!-- 绑定-->
//注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
1.5 测试类
使用目标对象id-----sumServiceImpl
public class Mytest01 {
@Test
public void test01(){
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
SumService s = (SumService) ac.getBean("sumServiceImpl");
String ans = s.doSome("胡鳖",11);
System.out.println(ans);
}
@Test
public void test02(){
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
SumService s = (SumService) ac.getBean("sumServiceImpl");
String ans = s.show();
System.out.println(ans);
}
}
二、@AfterReturning 后置通知
在目标方法执行之后执行。由
于是目标方法之后执行,所以可以获取到目标方法的返回值。
该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。
该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
2.1创建业务接口
public interface SumService {
String doSome(String name,int age);
String show();
}
2.2创建业务实现
@Service
public class SumServiceImpl implements SumService {
@Override
public String doSome(String name, int age) {
System.out.println("doSome业务功能实现。。。。");
return "aaabbbccc";
}
@Override
public String show() {
System.out.println("show的业务功能实现。。。。");
return "111";
}
}
2.3创建切面类,实现切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component
public class MyAspectJ {
/**
* 所以切面功能都是有切面方法实现的
* 可以将各种切面都在此类中开发
*
* 前置通知的切面方法规范
* 1)访问权限:public
* 2)方法返回值 void
* 3)方法名称自定义
* 4)方法有参数 (也可以没参数,如果目标方法没有返回值 ,则可以写无)
* 5) 必须使用@AfterReturning 注解来声明切入时机是前切功能和切入点\
* value: 制定切入点表达式
* returning : 制定目标方法的返回值名称,则名称必须与切面方法参数名称一至
*/
@AfterReturning(value = "execution(* com.bjpowernode.s02.SumServiceImpl.*(..))",returning = "obj")
public void myAfter(Object obj){
System.out.println("后切方法实现。。。。");
if(obj!=null){
if(obj instanceof String){
obj = obj.toString().toUpperCase();
System.out.println("通知中返回值: "+obj);
}else if(obj instanceof Student){
Student stu = (Student) obj;
stu.setName("李四");
System.out.println("切面方法的返回值:" + obj);
}
}
}
}
2.4 在.xml配置文件里进行切面绑定
<!-- 包扫描 创建对象-->
//包扫描 创建切面类对象 和 目标类对象
<context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>
<!-- 绑定-->
//注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.5 测试类
使用目标对象id-----sumServiceImpl
public class Mytest01 {
@Test
public void test01(){
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
SumService s = (SumService) ac.getBean("sumServiceImpl");
String ans = s.doSome("胡鳖",11);
System.out.println(ans);
}
三、(4) @Around 环绕通知
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。
并且方法可以包含一个 ProceedingJoinPoint 类型的参数。
接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
3.1创建业务接口
public interface SumService {
String doSome(String name,int age);
}
3.2创建业务实现
@Service
public class SumServiceImpl implements SumService {
@Override
public String doSome(String name, int age) {
System.out.println("doSome业务功能实现。。。。");
return "aaabbbccc";
}
3.3创建切面类,实现切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component
public class MyAspectJ {
/**
* 所以切面功能都是有切面方法实现的
* 可以将各种切面都在此类中开发
* <p>
* 前置通知的切面方法规范
* 1)访问权限:public
* 2)方法返回值 目标函数的返回值
* 3)方法名称自定义
* 4)方法有参数 为目标函数
* 5) 回避异常 Throwable
* 6) 必须使用@Around 注解来声明切入时机是前切功能和切入点
* value: 制定切入点表达式
*/
@Around(value = "execution(* com.bjpowernode.s03.SumServiceImpl.*(..))")
public Object myAfter(ProceedingJoinPoint pip) throws Throwable {
//前切功能实现
System.out.println("环绕通知功能后置功能实现。。。。");
//目标方法调用
Object obj = pip.proceed(pip.getArgs());
//后切功能实现
System.out.println("环绕通知后置功能实现");
return obj.toString().toUpperCase();
}
}
3.4 在.xml配置文件里进行切面绑定
<!-- 包扫描 创建对象-->
//包扫描 创建切面类对象 和 目标类对象
<context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>
<!-- 绑定-->
//注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3.5 测试类
使用目标对象id-----sumServiceImpl
public class Mytest01 {
@Test
public void test01(){
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
SumService s = (SumService) ac.getBean("sumServiceImpl");
String ans = s.doSome("胡鳖",11);
System.out.println(ans);
}
四. @After 最终通知
无论目标方法是否抛出异常,该增强均会被执行。