上一篇博客介绍了 Spring AOP 的概念、动态代理模式和入门案例,本片博客将详细介绍 AOP 的配置及使用相关的细节,包括 切入点表达式、基于 xml 配置 AOP,基于注解配置 AOP,基于注解配置类配置AOP。
上一篇博客链接:Spring AOP 概念及动态代理模式
博客展示 demo 的 github 地址:https://github.com/Jacks5320/SpringAopStudy
正式进行 AOP 编程之前,先了解一个核心概念————切点表达式。
作用:让 Spring 框架知道是对哪个类的哪个方法进行增强。
语法结构:[权限修饰符][返回值类型][类的全限定类名][方法名称](参数列表)
使用 execution
来解析切入点表达式:execution([权限修饰符][返回值类型][类的全限定类名][方法名称](参数列表))
以 com.jk.xml.service.impl.AccountServiceImpl.saveAccount()
方法为例,切点表达式如下所示:
标准写法:public void com.jk.xml.service.impl.AccountServiceImpl.saveAccount()
省略访问修饰符:void com.jk.xml.service.impl.AccountServiceImpl.saveAccount()
使用通配符表示任意类型的返回值:* com.jk.xml.service.impl.AccountServiceImpl.saveAccount()
/* 包结构通配
使用通配符表示包名:* *.*.*.*.*.AccountServiceImpl.saveAccount(),有几级包就得写几个 *. 来通配
使用 .. 表示子包:* *..AccountServiceImpl.saveAccount()
*/
/* 类和方法通配
使用通配符来表示类名:* *..*.saveAccount()
使用通配符来表示方法名:* *..AccountServiceImpl.*(),这样只会通配没有参数列表的方法
*/
/* 参数列表通配
基本类型通配:* *..AccountServiceImpl.*(int),表示通配只有一个参数,且参数为 int 类型,如果有多个参数,可以用逗号隔开进行通配。
引用类型:* *..AccountServiceImpl.*(java.util.List)
*/
全通配写法:* *..*.*(..)
以上只是介绍切入点表达式的一些规则和写法,可以按需搭配。
实际开发中的通常写法如下:* com.jk.xml.service.impl.*.*(..),表示业务逻辑层的所有类和方法
*
代表任意。(任意权限修饰,任意方法,任意包,任意类)。..
可以表示当前包的子包,也可以表示任意参数列表。<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.8.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.8.RELEASEversion>
dependency>
dependencies>
public interface AccountService {
//模拟保存
void saveAccount();
//模拟更新
void updateAccount(int id);
//模拟删除
int deleteAccount();
}
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
System.out.println("保存方法执行了。。。");
// int a = 1/0; //制造异常通知
}
@Override
public void updateAccount(int id) {
System.out.println("更新方法执行了。。。");
}
@Override
public int deleteAccount() {
System.out.println("删除方法执行了。。。");
return 0;
}
}
public class AopUtil {
//前置通知
public void beforeAdvice() {
System.out.println("<==before,前置通知加上了==>");
}
//后置通知
public void afterReturningAdvice() {
System.out.println("<==afterReturning,后置通知加上了==>");
}
//异常通知
public void afterThrowingAdvice() {
System.out.println("<==afterThrowing,异常通知加上了==>");
}
//最终通知
public void afterAdvice() {
System.out.println("<==after,最终通知加上了==>");
}
//环绕通知
public Object aroundAdvice(ProceedingJoinPoint jp) {
Object rtValue = null;
try {
Object[] args = jp.getArgs(); //获取执行方法的参数
System.out.println("<==around,环绕通知在之前加上了,before==>"); //写在 proceed 之前表示前置通知
rtValue = jp.proceed(args); //明确调用切入点方法
System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 proceed 之后表示后置通知
return rtValue;
} catch (Throwable t) {
System.out.println("<==around,环绕通知在之后加上了,afterThrowing==>");//写在 catch 里面表示异常通知
throw new RuntimeException(t);
} finally {
System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 finally 里面表示最终通知
}
}
}
<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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountService" class="com.jk.xml.service.impl.AccountServiceImpl"/>
<bean id="aopUtil" class="com.jk.xml.utils.AopUtil"/>
<aop:config>
<aop:aspect id="logAdvice" ref="aopUtil">
<aop:before method="beforeAdvice" pointcut="execution(* com.jk.xml.service.impl.*.saveAccount(..))"/>
<aop:after-returning method="afterReturningAdvice" pointcut-ref="aopPoint"/>
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="aopPoint"/>
<aop:after method="afterAdvice" pointcut-ref="aopPoint"/>
<aop:pointcut id="aopPoint" expression="execution(* com.jk.xml.service.impl.*.saveAccount(..))"/>
aop:aspect>
aop:config>
beans>
aop
名称空间。
标签表示是 AOP 的配置。
标签中的
标签表示配置的切面,id 属性是这个切面的唯一标识,可以被其他切面引用,ref 属性指定增强类的 id 属性。
标签中的通知类标签
xxx 表示通知类型,method 属性表示通知类中的增强方法,pointcut 属性使用切入点表达式指向增强的方法。
标签提取通用的切入点表达式,然后使用通知类型标签
中的 pointcut-ref 属性来引入公共切入点表达式的 id 属性。
标签,如果写在
标签外部,必须在引用切面的前面定义,如果是在内部,则可以放任意位置,为了规范,一般都放在引用位置的前面。@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:xml/bean2.xml"})
public class TestBean2 {
@Autowired
AccountService as;
//测试 前置通知、后置通知、异常通知和最终通知
@Test
public void testBeforeAdvice(){
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}
打印结果:
<==before,前置通知加上了==>
保存方法执行了。。。
<==afterReturning,后置通知加上了==>
<==after,最终通知加上了==>
更新方法执行了。。。
删除方法执行了。。。
execution(* com.jk.xml.service.impl.*.saveAccount(..))
表示值拦截 saveAccount()
方法,所以只有这个方法被增强了。afterReturning
和 异常通知 afterThrowing
永远不会同时出现。之所以环绕通知单独提取出来,是因为,在环绕通知中可以涵盖其他通知,而且长得更像上篇博客介绍的动态代理模式。
public class AopUtil {
public Object aroundAdvice(ProceedingJoinPoint jp) {
Object rtValue = null;
try {
Object[] args = jp.getArgs();//获取执行方法的参数
System.out.println("<==around,环绕通知在之前加上了,before==>");//写在 proceed 之前表示前置通知
rtValue = jp.proceed(args);//明确调用切入点方法
System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 proceed 之后表示后置通知
return rtValue;
} catch (Throwable t) {
System.out.println("<==around,环绕通知在之后加上了,afterThrowing==>");//写在 catch 里面表示异常通知
throw new RuntimeException(t);
} finally {
System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 finally 里面表示最终通知
}
}
}
JoinPoint
的类视图:ProceedingJoinPoint
是 JoinPoint
的子接口,都是由 AspectJ
提供,用于管理连接点的。java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advice
。JoinPointImpl
和 MethodInvocationProceedingJoinPoint
。这里就不展开了,介绍几个与代理相关的方法:
getTarget().getClass()
:获取被代理对象getThis().getClass()
:获取代理的对象getSignature().getName()
:获取当前拦截的方法名getArgs()
:获取当前拦截方法的参数数组,可以使用索引获取参数。这里依然使用上面的实现类和接口。
@Component
@Aspect//表示当前类是一个切面类
public class AopUtil {
//指定切入点表达式
@Pointcut("execution(* com.jk.annotation.service.impl.*.*(..))")
private void aopAdvice() {
}
//前置通知
@Before("aopAdvice()")
public void beforeAdvice() {
System.out.println("<==before,前置通知加上了==>");
}
//后置通知
@AfterReturning("aopAdvice()")
public void afterReturningAdvice() {
System.out.println("<==afterReturning,后置通知加上了==>");
}
//异常通知
@AfterThrowing("aopAdvice()")
public void afterThrowingAdvice() {
System.out.println("<==afterThrowing,异常通知加上了==>");
}
//最终通知
@After("aopAdvice()")
public void afterAdvice() {
System.out.println("<==after,最终通知加上了==>");
}
}
@Aspect
等价于 xml 中的
@Pointcut
等价于 xml 中的
@Before("aopAdvice()")
等价于 xml 中的
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jk.annotation"/>
<aop:aspectj-autoproxy/>
beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:annotation/bean.xml")
public class TestDemo {
@Autowired
private AccountService as;
//测试注解
@Test
public void testAnnotation(){
as.saveAccount();
}
}
执行结果:
<==before,前置通知加上了==>
保存方法执行了。。。
<==afterReturning,后置通知加上了==>
<==after,最终通知加上了==>
@Component
@Aspect//表示当前类是一个切面类
public class AopUtil {
@Around("aopAdvice()")
public Object aroundAdvice(ProceedingJoinPoint jp) {
Object rtValue = null;
try {
Object[] args = jp.getArgs(); //获取执行方法的参数
System.out.println("<==around,环绕通知在之前加上了,before==>"); //写在 proceed 之前表示前置通知
rtValue = jp.proceed(args); //明确调用切入点方法
System.out.println("<==around,环绕通知在之后加上了,afterReturning==>");//写在 proceed 之后表示后置通知
return rtValue;
} catch (Throwable t) {
System.out.println("<==around,环绕通知在之后加上了,afterThrowing==>"); //写在 catch 里面表示异常通知
throw new RuntimeException(t);
} finally {
System.out.println("<==around,环绕通知在最后加上了,after==>"); //写在 finally 里面表示最终通知
}
}
}
执行结果:
<==around,环绕通知在之前加上了,before==>
保存方法执行了。。。
<==around,环绕通知在之后加上了,afterReturning==>
<==around,环绕通知在最后加上了,after==>
完全注解配置的意思指的是不需要任何的 xml 配置都可以运行,这里只需要添加一个配置注解类,删除配置文件即可。
@Configuration
@ComponentScan(basePackages = "com.jk.annotation")
@EnableAspectJAutoProxy //等同于
public class SpringConfig {
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class TestDemo2 {
@Autowired
AccountService as;
@Test
public void fullAnnotation(){
as.saveAccount();
}
}
@ContextConfiguration
的值换成了配置类。以上就是 AOP 相关的配置和使用方法了,希望能帮助你理解 AOP 的概念。