Spring AOP 配置方式
目前 Spring AOP 一共有三种配置方式。
- Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的。
- Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间
<aop />
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。
基于接口的配置
这是Spring 1.2 基于接口的配置,这是最古老的配置,最早的 Spring AOP 是完全基于几个接口的,想看源码的同学可以从这里起步。
业务类
package com.eva.service;
import com.eva.pojo.User;
public interface UserService {
User createUser(String name, Integer age);
User findUser(String name);
}
package com.eva.service;
import com.eva.pojo.Order;
public interface OrderService {
Order createOrder(String name, String product);
Order findOrder(String name);
}
package com.eva.service.impl;
import com.eva.pojo.User;
import com.eva.service.UserService;
public class UserServiceImpl implements UserService {
private static User user = null;
@Override
public User createUser(String name, Integer age) {
user = new User();
user.setName(name);
user.setAge(age);
return user;
}
@Override
public User findUser(String name) {
return user;
}
}
package com.eva.service.impl;
import com.eva.pojo.Order;
import com.eva.service.OrderService;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderService {
private static Order order = null;
@Override
public Order createOrder(String name, String product) {
order = new Order();
order.setName(name);
order.setProduct(product);
return order;
}
@Override
public Order findOrder(String name) {
return order;
}
}
advice
前置通知
package com.eva.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
public class LogArgsAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("准备执行方法: " + method.getName() + ", 参数列表:" + Arrays.toString(args));
}
}
后置通知 AfterReturningAdvice
package com.eva.advice;
import org.springframework.aop. AfterReturningAdvice;
import java.lang.reflect.Method;
public class LogResultAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("方法返回: " + returnValue);
}
}
ProxyFactoryBean方式
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 目标类 -->
<bean id="orderServiceImpl" class="com.eva.service.impl.OrderServiceImpl"/>
<bean id="userServiceImpl" class="com.eva.service.impl.UserServiceImpl"/>
<!-- 定义两个 advice -->
<bean id="logArgsAdvice" class="com.eva.advice.LogArgsAdvice"/>
<bean id="logResultAdvice" class="com.eva.advice.LogResultAdvice"/>
<bean id="logCreateAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!--advisor 实例的内部会有一个 advice-->
<property name="advice" ref="logArgsAdvice"/>
<!--只有下面这两个方法才会被拦截-->
<property name="mappedNames" value="createUser,createOrder"/>
</bean>
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--代理的接口-->
<property name="proxyInterfaces">
<list>
<value>com.eva.service.UserService</value>
</list>
</property>
<!--代理的具体实现-->
<property name="target" ref="userServiceImpl"/>
<!--配置拦截器,这里可以配置 advice、advisor、interceptor-->
<property name="interceptorNames">
<list>
<value>logCreateAdvisor</value>
</list>
</property>
</bean>
</beans>
test类
package com.eva;
import com.eva.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringAOPTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring1.xml");
// 这里要取 AOP 代理:userServiceProxy,这非常重要
UserService userService = (UserService) context.getBean("userServiceProxy");
userService.createUser("evan",18);
}
}
结果
准备执行方法: createUser, 参数列表:[evan, 18]
可以看到,userServiceProxy 这个 bean 配置了一个 advisor,advisor 内部有一个 advice。advisor 负责匹配方法,内部的 advice 负责实现方法包装
BeanNameAutoProxyCreator
获取 bean 的时候需要获取这个代理类的 bean 实例(如 (UserService) context.getBean("userServiceProxy")),这显然非常不方便,不利于之后要使用的自动根据类型注入。下面介绍 autoproxy 的解决方案。
autoproxy:从名字也可以看出来,它是实现自动代理,也就是说当 Spring 发现一个 bean 需要被切面织入的时候,Spring 会自动生成这个 bean 的一个代理来拦截方法的执行,确保定义的切面能被执行。
这里强调自动,也就是说 Spring 会自动做这件事,而不用像前面介绍的,需要显式地指定代理类的 bean。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 目标类 -->
<bean id="orderServiceImpl" class="com.eva.service.impl.OrderServiceImpl"/>
<bean id="userServiceImpl" class="com.eva.service.impl.UserServiceImpl"/>
<!-- 定义两个 advice -->
<bean id="logArgsAdvice" class="com.eva.advice.LogArgsAdvice"/>
<bean id="logResultAdvice" class="com.eva.advice.LogResultAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>logArgsAdvice</value>
<value>logResultAdvice</value>
</list>
</property>
<property name="beanNames" value="*ServiceImpl"/>
</bean>
</beans>
去掉原来的 ProxyFactoryBean 的配置,改为使用 BeanNameAutoProxyCreator 来配置
test 类
package com.eva;
import com.eva.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Spring_AOP_BeanNameAutoProxy {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring2.xml");
// 不再需要根据代理找 bean
UserService userService = context.getBean(UserService.class);
userService.createUser("evan", 18);
}
}
结果
准备执行方法: createUser, 参数列表:[evan, 18]
方法返回: User(name=evan, age=18)
BeanNameAutoProxyCreator :需要指定被拦截类名的模式(如 *ServiceImpl),它可以配置多次,这样就可以用来匹配不同模式的类了。
DefaultAdvisorAutoProxyCreator
advisor 内部包装了 advice,advisor 负责决定拦截哪些方法,内部 advice 定义拦截后的逻辑。所以,仔细想想其实就是只要让我们的 advisor 全局生效就能实现我们需要的自定义拦截功能、拦截后的逻辑处理。也就是说,我们能通过配置 Advisor(通过RegexpMethodPointcutAdvisor,它可以实现正则匹配,以此来实现自定义拦截的功能。),精确定位到需要被拦截的方法,然后使用内部的 Advice 执行逻辑处理。
- BeanNameAutoProxyCreator 是自己匹配方法,然后交由内部配置 advice 来拦截处理;
- DefaultAdvisorAutoProxyCreator 是让 ioc 容器中的所有 advisor 来匹配方法,advisor 内部都是有 advice 的,让它们内部的 advice 来执行拦截处理。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 目标类 -->
<bean id="orderServiceImpl" class="com.eva.service.impl.OrderServiceImpl"/>
<bean id="userServiceImpl" class="com.eva.service.impl.UserServiceImpl"/>
<!-- 定义两个 advice -->
<bean id="logArgsAdvice" class="com.eva.advice.LogArgsAdvice"/>
<bean id="logResultAdvice" class="com.eva.advice.LogResultAdvice"/>
<!-- 定义两个 advisor -->
<!-- 记录create* 方法的传参 -->
<bean id="logArgsAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="logArgsAdvice"/>
<property name="pattern" value="com.eva.service.*.create.*"/>
</bean>
<!-- 记录query* 返回值 -->
<bean id="logResultAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="logResultAdvice"/>
<property name="pattern" value="com.eva.service.*.find.*"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
Test类
package com.eva;
import com.eva.service.OrderService;
import com.eva.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SPRING_AOP_DefaultAdvisorAutoProxy {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring3.xml");
UserService userService = context.getBean(UserService.class);
OrderService orderService = context.getBean(OrderService.class);
userService.createUser("evan", 18);
orderService.createOrder("evan","iphone15");
orderService.findOrder("evan");
}
}
结果
准备执行方法: createUser, 参数列表:[evan, 18]
准备执行方法: createOrder, 参数列表:[evan, iphone15]
方法返回: Order(name=evan, product=iphone15)
Spring 2.0 @AspectJ 配置
Spring 2.0 以后,引入了 @AspectJ 和 Schema-based 的两种配置方式,先来介绍 @AspectJ 的配置方式,之后再来看使用 xml 的配置方式。
依赖
首先,需要依赖 aspectjweaver.jar 这个包,这个包来自于 AspectJ:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.11</version>
</dependency>
如果是使用 Spring Boot 的话,添加以下依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因为需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。
@AspectJ 采用注解的方式来配置使用 Spring AOP。
开启 @AspectJ 的注解配置方式
首先,需要开启 @AspectJ 的注解配置方式,有两种方式:
使用 xml 配置:
<aop:aspectj-autoproxy/>
使用 @EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,称之为一个 Aspect。
接下来,需要关心的是 @Aspect 注解的 bean 中,需要配置哪些内容。
配置 Pointcut
首先,需要配置 Pointcut,Pointcut 在大部分地方被翻译成切点,用于定义哪些方法需要被增强或者说需要被拦截
,有点类似于之前介绍的 Advisor 的方法匹配。
Spring AOP 只支持 bean 中的方法,所以可以认为 Pointcut 就是用来匹配 Spring 容器中的所有 bean 的方法的。
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
可以看到,@Pointcut 中使用了 execution 来正则匹配方法签名,这也是最常用的,除了 execution,还有其他的几个比较常用的匹配方式:
- within:指定所在类或所在包下面的方法(Spring AOP 独有)如
@Pointcut("within(com.***.service..*)")
- @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。如
@Pointcut("execution(.*(..)) && @annotation(com.xxx.Subscribe)")
- bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)如
@Pointcut("bean(*Service)")
Tips:上面匹配中,通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。
配置 Advice
@Aspect
public class AdviceExample {
// 这里会用到我们前面说的 SystemArchitecture
// 下面方法就是写拦截 "dao层实现"
@Before("com.eva.aop.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ... 实现代码
}
// 当然,我们也可以直接"内联"Pointcut,直接在这里定义 Pointcut
// 把 Advice 和 Pointcut 合在一起了,但是这两个概念我们还是要区分清楚的
@Before("execution(* com.eva.dao.*.*(..))")
public void doAccessCheck() {
// ... 实现代码
}
@AfterReturning("com.eva.aop.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning(
pointcut="com.eva.aop.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// 这样,进来这个方法的处理时候,retVal 就是相应方法的返回值,是不是非常方便
// ... 实现代码
}
// 异常返回
@AfterThrowing("com.eva.aop.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ... 实现代码
}
@AfterThrowing(
pointcut="com.eva.aop.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ... 实现代码
}
// 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
@After("com.eva.aop.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// 通常就像 finally 块一样使用,用来释放资源。
// 无论正常返回还是异常退出,都会被拦截到
}
// 感觉这个很有用吧,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
@Around("com.eva.aop.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
有些 Advice 缺少方法传参,如在 @Before 场景中参数往往是非常有用的,比如要用日志记录下来被拦截方法的入参情况。
Spring 提供了非常简单的获取入参的方法,使用 org.aspectj.lang.JoinPoint 作为 Advice 的第一个参数即可,如:
@Before("com.eva.aop_spring_2_aspectj.SystemArchitecture.businessService()")
public void logArgs(JoinPoint joinPoint) {
System.out.println("方法执行前,打印入参:" + Arrays.toString(joinPoint.getArgs()));
}
Demo
全注解的DEMO
业务类
package com.evan.service;
import com.evan.pojo.User;
public interface UserService {
User createUser(String name, Integer age);
User findUser(String name);
User queryUser();
}
package com.evan.service.impl;
import com.evan.pojo.User;
import com.evan.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private static User user = null;
@Override
public User createUser(String name, Integer age) {
user = new User();
user.setName(name);
user.setAge(age);
return user;
}
@Override
public User findUser(String name) {
return user;
}
@Override
public User queryUser() {
return user;
}
}
切面功能
package com.evan.aspect;
import org.aspectj.lang.JoinPoint;
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;
@Aspect
@Component
public class LogArgsAspect {
// 这里可以设置一些自己想要的属性,到时候在配置的时候注入进来
@Before("pointCutMethod()")
public void logArgs(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("" + joinPoint.getSignature().getName() + "运行。。。@Before:参数列表是:{" + Arrays.asList(args) + "}");
}
/**
* define point cut.
*/
@Pointcut("execution(* com.evan.service.*.*(..))")
private void pointCutMethod() {
}
}
package com.evan.aspect;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogResultAspect {
@AfterReturning(pointcut = "pointCutMethod()", returning = "result")
public void logResult(Object result) {
System.out.println("[@AspectJ]返回值:" + result);
}
/**
* define point cut.
*/
@Pointcut("execution(* com.evan.service.*.*(..))")
private void pointCutMethod() {
}
}
package com.evan.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.evan")
@EnableAspectJAutoProxy
public class MyConfig {
}
测试类
package com.evan;
import com.evan.config.MyConfig;
import com.evan.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Spring_AOP_Annotation {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = context.getBean(UserService.class);
userService.createUser("evan", 11);
userService.findUser("evan");
}
}
结果
createUser运行。。。@Before:参数列表是:{[evan, 11]}
[@AspectJ]返回值:User(name=evan, age=11)
findUser运行。。。@Before:参数列表是:{[evan]}
[@AspectJ]返回值:User(name=evan, age=11)
注解+xml配置
通知
package com.evan.aspect;
import org.aspectj.lang.JoinPoint;
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;
@Aspect
public class LogArgsAspect {
// 这里可以设置一些自己想要的属性,到时候在配置的时候注入进来
@Before("pointCutMethod()")
public void logArgs(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("" + joinPoint.getSignature().getName() + "运行。。。@Before:参数列表是:{" + Arrays.asList(args) + "}");
}
/**
* define point cut.
*/
@Pointcut("execution(* com.evan.service.*.*(..))")
private void pointCutMethod() {
}
}
package com.evan.aspect;
import org.aspectj.lang.JoinPoint;
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;
@Aspect
public class LogArgsAspect {
// 这里可以设置一些自己想要的属性,到时候在配置的时候注入进来
@Before("pointCutMethod()")
public void logArgs(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("" + joinPoint.getSignature().getName() + "运行。。。@Before:参数列表是:{" + Arrays.asList(args) + "}");
}
/**
* define point cut.
*/
@Pointcut("execution(* com.evan.service.*.*(..))")
private void pointCutMethod() {
}
}
xml
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 -->
<bean id="orderService" class="com.evan.service.impl.OrderServiceImpl"/>
<bean id="userService" class="com.evan.service.impl.UserServiceImpl"/>
<!-- 定义两个 aspect -->
<bean id="logArgsAspect" class="com.evan.aspect.LogArgsAspect"/>
<bean id="logResultAspect" class="com.evan.aspect.LogResultAspect"/>
<!--开启 @AspectJ 配置-->
<aop:aspectj-autoproxy/>
</beans>
测试类
package com.evan;
import com.evan.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Spring_AOP_XML {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean(UserService.class);
userService.createUser("evan", 11);
userService.findUser("evan");
}
}
结果
createUser运行。。。@Before:参数列表是:{[evan, 11]}
[@AspectJ]返回值:User(name=evan, age=11)
findUser运行。。。@Before:参数列表是:{[evan]}
[@AspectJ]返回值:User(name=evan, age=11)
Spring 2.0 schema-based 配置
Spring 2.0 以后提供了基于 命名空间的 XML 配置。这里说的 schema-based 就是指基于 aop 这个 schema。
Spring 通过 *NamespaceHandler 解析各种命名空间的,解析 的源码在 org.springframework.aop.config.AopNamespaceHandler 中。
切面
package com.evan.aspect;
import org.aspectj.lang.JoinPoint;
import java.util.Arrays;
public class LogArgsAspect {
// 这里可以设置一些自己想要的属性,到时候在配置的时候注入进来
public void logArgs(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("" + joinPoint.getSignature().getName() + "运行。。。@Before:参数列表是:{" + Arrays.asList(args) + "}");
}
}
package com.evan.aspect;
public class LogResultAspect {
public void logResult(Object result) {
System.out.println("[@AspectJ]返回值:" + result);
}
}
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 -->
<bean id="orderServiceImpl" class="com.evan.service.impl.OrderServiceImpl"/>
<bean id="userServiceImpl" class="com.evan.service.impl.UserServiceImpl"/>
<!-- 定义两个 advice -->
<bean id="logArgsAspect" class="com.evan.aspect.LogArgsAspect"/>
<bean id="logResultAspect" class="com.evan.aspect.LogResultAspect"/>
<aop:config>
<!--下面这两个 Pointcut 是全局的,可以被所有的 Aspect 使用-->
<!--这里示意了两种 Pointcut 配置-->
<aop:pointcut id="logArgsPointcut" expression="execution(* com.evan.service.*.*(..))"/>
<aop:pointcut id="logResultPointcut"
expression="com.evan.aspect.SystemArchitecture.businessService()"/>
<aop:aspect ref="logArgsAspect">
<!--在这里也可以定义 Pointcut,不过这是局部的,不能被其他的 Aspect 使用-->
<aop:pointcut id="internalPointcut"
expression="com.evan.aspect.SystemArchitecture.businessService()"/>
<aop:before method="logArgs" pointcut-ref="internalPointcut"/>
</aop:aspect>
<aop:aspect ref="logArgsAspect">
<aop:before method="logArgs" pointcut-ref="logArgsPointcut"/>
</aop:aspect>
<aop:aspect ref="logResultAspect">
<aop:after-returning method="logResult" returning="result" pointcut-ref="logResultPointcut"/>
</aop:aspect>
</aop:config>
</beans>
测试类
package com.evan;
import com.evan.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Spring_AOP_Basic_Schema_Based {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean(UserService.class);
userService.createUser("evan", 11);
userService.findUser("evan");
}
}
结果
createUser运行。。。@Before:参数列表是:{[evan, 11]}
createUser运行。。。@Before:参数列表是:{[evan, 11]}
[@AspectJ]返回值:User(name=evan, age=11)
findUser运行。。。@Before:参数列表是:{[evan]}
findUser运行。。。@Before:参数列表是:{[evan]}
[@AspectJ]返回值:User(name=evan, age=11)
DEMO
xml配置方式
切面
package com.evan.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
//定义该方法是一个前置通知
public void before() {
System.out.println("前置通知");
}
public void after() {
System.out.println("后置通知");
}
public void afterReturning(Object result) {
System.out.println("返回通知(After-returning)" + result);
}
public void afterThrowing(Exception e) {
System.out.println("异常通知(After-throwing), 异常: " + e.getMessage());
}
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法");
Object o = pjp.proceed();
System.out.println(o);
System.out.println("环绕通知: 退出方法");
return o;
}
}
功能代码
package com.evan.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
}
package com.evan.service;
public interface ArithmeticCalculator {
Integer add(int i, int j);
Integer sub(int i, int j);
Integer mul(int i, int j);
Integer div(int i, int j);
}
package com.evan.service.impl;
import com.evan.service.ArithmeticCalculator;
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public Integer add(int i, int j) {
return i + j;
}
@Override
public Integer sub(int i, int j) {
return i - j;
}
@Override
public Integer mul(int i, int j) {
return i * j;
}
@Override
public Integer div(int i, int j) {
return i / j;
}
}
<?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"
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">
<aop:aspectj-autoproxy/>
<!-- 目标类 -->
<bean id="arithmeticCalculator" class="com.evan.service.impl.ArithmeticCalculatorImpl">
</bean>
<!-- 切面 -->
<bean id="myAspect" class="com.evan.aspect.MyAspect">
</bean>
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut id="pointCutMethod" expression="execution(* com.evan.service.*.*(..))"/>
<!-- 环绕通知 -->
<aop:around method="doAround" pointcut-ref="pointCutMethod"/>
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointCutMethod"/>
<!-- 后置通知;returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="afterReturning" pointcut-ref="pointCutMethod" returning="result"/>
<!-- 异常通知:如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称、类型-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCutMethod" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="pointCutMethod"/>
</aop:aspect>
</aop:config>
</beans>
main 方法
package com.evan;
import com.evan.service.ArithmeticCalculator;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
@Test
public void test01() {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
ArithmeticCalculator arithmeticCalculator = context.getBean(ArithmeticCalculator.class);
arithmeticCalculator.add(1, 2);
}
@Test
public void test03() {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
ArithmeticCalculator arithmeticCalculator = context.getBean(ArithmeticCalculator.class);
try {
arithmeticCalculator.div(1, 0);
} catch (Exception e) {
//ignore
}
}
}
test01 测试结果
-----------------------
环绕通知: 进入方法
前置通知
3
环绕通知: 退出方法
返回通知(After-returning)3
后置通知
test03 测试结果
-----------------------
环绕通知: 进入方法
前置通知
异常通知(After-throwing), 异常: / by zero
后置通知
注解方式
package com.evan.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
//相当于<aop:aspectj-proxy>
public class MyAspect {
//定义该方法是一个前置通知
@Before("execution(* com.evan.service.*.*(..))")
public void before() {
System.out.println("注解前置通知");
}
@AfterReturning(value = "pointcut()", returning = "returnVal")
public void afterReturning(Object returnVal) {
System.out.println("返回通知(After-returning)" + returnVal);
}
// 切点方法执行后运行,不管切点方法执行成功还是出现异常
@After(value = "pointcut()")
public void doAfter(JoinPoint joinPoint) {
System.out.println("注解后置通知 After 方法执行 - Finish time: " + System.currentTimeMillis());
}
// 切点方法成功执行返回后运行
@AfterReturning(value = "pointcut()", returning = "returnValue")
public void doAfterReturning(JoinPoint joinPoint, Object returnValue) {
System.out.println("AfterReturning 方法执行 - Returned value: " + returnValue);
}
// 切点方法成功执行返回后运行
@AfterThrowing(value = "pointcut()", throwing = "throwing")
public void doAfterThrowing(JoinPoint joinPoint, Object throwing) {
System.out.println("异常通知(After-throwing), 方法执行 - throwing value: " + throwing);
}
@Around(value = "pointcut()")
public Object doAround(ProceedingJoinPoint pjp) {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法");
Object o = null;
try {
o = pjp.proceed();
} catch (Throwable e) {
//throw new RuntimeException(e);
System.out.println(e.getMessage());
} finally {
System.out.println("环绕通知: 退出方法");
}
return o;
}
@Pointcut(value = "execution(* com.evan.service.*.*(..))")
public void pointcut() {
}
}
package com.evan.config;
import com.evan.aspect.MyAspect;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configurable
@EnableAspectJAutoProxy
@ComponentScan("com.evan")
public class MyConfig {
@Bean
public MyAspect myAspect() {
return new MyAspect();
}
}
package com.evan.service;
public interface ArithmeticCalculator {
Integer add(int i, int j);
Integer sub(int i, int j);
Integer mul(int i, int j);
Integer div(int i, int j);
}
package com.evan.service.impl;
import com.evan.service.ArithmeticCalculator;
import org.springframework.stereotype.Service;
@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
package com.evan;
import com.evan.config.MyConfig;
import com.evan.service.ArithmeticCalculator;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AopTest {
@Test
public void test01() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
ArithmeticCalculator arithmeticCalculator = context.getBean(ArithmeticCalculator.class);
arithmeticCalculator.add(1, 2);
}
@Test
public void test03() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
ArithmeticCalculator arithmeticCalculator = context.getBean(ArithmeticCalculator.class);
try {
arithmeticCalculator.div(1, 0);
} catch (Exception e) {
//ignore
}
}
}