Spring Aop demo

83 阅读12分钟

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
        }
    }
}