Spring切面AspectJ
2020.01.26 18:34
2020.02.24 16:16
1. AspectJ
1.1. 介绍
- AspsstJ是一个基于Java语言的AOP框架
- Spring2.0以后新增了对AspectU切点表达式支持
- @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。
- 新版本Spring框架,建议使用AspectJ方式来开发AOP
1.2. 切入点表达式【重点】
1、语法
访问修饰符 返回值类型(必填) 包和类 方法(必填)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
修饰符:
- public 公共方法
- * 任意
返回值:
- void 没有返回值
- String 字符串
- * 任意
包[可省略]
- com.misiai.other 固定包
- com.misiai.other.*.service other包下任意
- com.misiai.other.. (两个点)other包下所有包[可无限子包]
类[省略]
- UserServiceImpl 指定
- User* User开头
- *Impl Impl结尾
- * 任意
方法名[不可省略]
- AddUser 固定
- add* add开头
- *User User结尾
- * 任意
(参数)
- () 无参
- (int) 一个整型
- (int, int) 两个整型
- .. 任意参数
throws
- 一般不写
最常用:
execution(* com.misiai..*ServiceImpl.*(..))
//表示com.misiai包及其所有子包下所有的以ServiceImpl结尾的类生成代理对象。
1.3. AspectJ通知类型
- aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
- aspecti 通知类型,只定义类型名称、以及方法格式。
- before:前置通知,在切入点方法的前面显示;
- after-returning:后置通知,在切入点方法的后面显示;
- around:环绕通知,十分强大,可以做任何事情,在切入点前后显示;可以实现其它4个的功能,主要掌握它。
- after-throwing:抛出异常通知,当出现异常时显示;
- after:最终通知,不管发生什么在切入点后面显示;
1.4. 导入jar包
- aop联盟 规范
- spring-aop
- aspect 规范
- spring-aspect
1.5. 基于XML
- 目标类:接口+实现类
- 切面类:编写多个通知,采用aspectJ,通知名称任意(方法名任意)
- aop编程,将通知应用到目标类。
- 测试
1.5.1. 目标类
package com.misiai.d_aspect.a_xml;
package com.misiai.d_aspect.a_xml;
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("d_aspect.a_xml:Add User");
Integer a = 1 / 0;
}
@Override
public String updateUser() {
System.out.println("d_aspect.a_xml:Update User");
return "更新成功";
}
@Override
public void deleteUser() {
System.out.println("d_aspect.a_xml:Delete User");
}
}
1.5.2. 切面类
package com.misiai.d_aspect.a_xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;
/**
* 切面类,具有多个通知
*/
public class MyAspect {
/**
* 前置通知
*/
public void before(JoinPoint joinPoint) {
/*JoinPoint,传递参数,有一些类的信息
* 比如获得方法名:joinPoint.getSignature().getName()
* */
System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
}
/*后置返回*/
public void afterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("后置返回通知:" + ret);
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
/*这里可以放前置通知*/
System.out.println("环绕方法-前置通知");
// 手动执行目标方法
Object proceed = joinPoint.proceed();
System.out.println("环绕方法-后置通知");
/*这里可以放后置/后置返回通知*/
return proceed;
}
/**
* 后置通知
*
* @param joinPoint
*/
public void after(JoinPoint joinPoint) {
/*比如这里还可以获得参数*/
System.out.println("after 参数:" + Arrays.asList(joinPoint.getArgs()));
System.out.println("后置通知 aspect");
System.out.println("====================");
}
//抛出异常通知
public void afterThrowing(JoinPoint joinPoint,Throwable throwable) {
System.out.println("抛出异常通知:"+throwable.getMessage());
}
}
1.5.3. 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--1.创建目标类-->
<bean id="userService" class="com.misiai.d_aspect.a_xml.UserServiceImpl"/>
<!--2.创建切面类,即通知-->
<bean id="aspect" class="com.misiai.d_aspect.a_xml.MyAspect"/>
<!--3.aop编程-->
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.misiai.d_aspect.a_xml.UserServiceImpl.*(..))"/>
<aop:aspect ref="aspect">
<!--
3.1前置通知
<aop:before method=""pointcut=""pointcut-ref=""/>
method:通知,及方法名
pointcut:切入点表达式,此表达式只能当前通知使用。
pointcut-ref:切入点引用,可以与其他通知共享切入点。
-->
<!--<aop:before method="before" pointcut-ref="pointCut"/>-->
<!--3.2后置返回通知-->
<!--<aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="ret"/>-->
<!--3.3后置通知-->
<!--<aop:after method="after" pointcut-ref="pointCut"/>-->
<!--3.4环绕通知
通知方法格式:public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
返回值类型:Object
方法名:任意
参数:org.aspectj.lang.ProceedingJoinPoint
抛出异常
执行目标方法:Object obi=joinPoint.proceed();
-->
<aop:around method="around" pointcut-ref="pointCut"/>
<!--3.5 抛出异常-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="throwable"/>
</aop:aspect>
</aop:config>
</beans>
1.5.4. 测试类
package com.misiai.d_aspect.a_xml;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAspectXml {
@Test
public void test01() {
String xmlPath = "com/misiai/d_aspect/a_xml/beans.xml";
ApplicationContext ap = new ClassPathXmlApplicationContext(xmlPath);
// 获得目标类
UserService userService = (UserService)ap.getBean("userService");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
1.6. 基于注解
1.6.1. 替换bean
<!--1.创建目标类-->
<bean id="userService" class="com.misiai.d_aspect.b_ann.UserServiceImpl"/>
<!--2.创建切面类,即通知-->
<bean id="aspect" class="com.misiai.d_aspect.b_ann.MyAspect"/>
把上面两个替换掉,改用注解方式
添加xml
<!--1.扫描注解类-->
<context:component-scan base-package="com.misiai.d_aspect.b_ann"/>
<!--2.确定aop注解生效-->
<aop:aspectj-autoproxy/>
1.6.2. 替换xml配置文件中的aop
<aop:config>
....
</aop:config>
在各通知方法上增加注解:
@Before("execution(* com.misiai.d_aspect.b_ann.UserServiceImpl.*(..))")
public void before(JoinPoint joinPoint) {
/*JoinPoint,传递参数,有一些类的信息
* 比如获得方法名:joinPoint.getSignature().getName()
* */
System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
}
1.6.3. 声明公共切入点表达式
前面发现,如果是加注解,那么每个注解的切入表达式都是一样的,那么怎么抽取公共?
1、随便声明一个方法,在其上面声明切入表达式
@Pointcut("execution(* com.misiai.d_aspect.b_ann.UserServiceImpl.*(..))")
public void myPointCut() {
}
2、使用
然后在需要使用的地方,加上value="方法名()"
@Before(value = "myPointCut()")
public void before(JoinPoint joinPoint) {
/*JoinPoint,传递参数,有一些类的信息
* 比如获得方法名:joinPoint.getSignature().getName()
* */
System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
}
2.1、如果有返回值的话,就使用:returning
@AfterReturning(value = "myPointCut()", returning = "ret")
public void afterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("后置返回通知:" + ret);
}
1.6.4. 最终代码
切面类
package com.misiai.d_aspect.b_ann;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 切面类,具有多个通知
*/
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.misiai.d_aspect.b_ann.UserServiceImpl.*(..))")
public void myPointCut() {
}
/**
* 前置通知
* 切入点当前有效
*/
// @Before(value = "myPointCut()")
public void before(JoinPoint joinPoint) {
/*JoinPoint,传递参数,有一些类的信息
* 比如获得方法名:joinPoint.getSignature().getName()
* */
System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
}
/**
* 后置返回
*/
// @AfterReturning(value = "myPointCut()", returning = "ret")
public void afterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("后置返回通知:" + ret);
}
/**
* 环绕通知
*
* @param joinPoint
* @return
* @throws Throwable
*/
// @Around(value = "myPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
/*这里可以放前置通知*/
System.out.println("环绕方法-前置通知");
// 手动执行目标方法
Object proceed = joinPoint.proceed();
System.out.println("环绕方法-后置通知");
/*这里可以放后置/后置返回通知*/
return proceed;
}
/**
* 后置通知
*
* @param joinPoint
*/
@After(value = "myPointCut()")
public void after(JoinPoint joinPoint) {
/*比如这里还可以获得参数*/
System.out.println("after 参数:" + Arrays.asList(joinPoint.getArgs()));
System.out.println("后置通知 aspect");
System.out.println("====================");
}
//抛出异常通知
// @AfterThrowing(value = "myPointCut()", throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
System.out.println("抛出异常通知:" + throwable.getMessage());
}
}
1.6.5. 注解总结
@Aspect://作用是把当前类标识为一个切面供容器读取
@Pointcut://Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around://环绕增强,相当于MethodInterceptor
@AfterReturning://后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before://标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing://异常抛出增强,相当于ThrowsAdvice
@After: //final增强,不管是抛出异常或者正常退出都会执行
本节阅读完毕!
(分享)