一、概述
1.1、官网
AOP的中文名称是面向切面编程或者面向方面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.2、通俗描述
不通过修改源代码的方式,在主干功能里面添加新功能。
1.3、案例说明
二、底层原理
AOP底层使用动态代理技术。有两种情况的动态代理:
- 有接口情况:JDK动态代理
- 无接口情况:CGLIB动态代理,基于类的继承,使用代理子类的形式,对父类的方法进行重写,完成方法的增强
2.1、JDK动态代理案例代码
2.1.1、UserDao
package org.star.dao;
public interface UserDao {
/**
* 两数相加
* @param num1
* @param num2
* @return
*/
int add(int num1,int num2);
/**
* 根据id修改
* @param id
* @return
*/
int update(long id);
}
2.1.2、UserDaoImpl
package org.star.dao.impl;
import org.star.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int update(long id) {
// do your business...
return 1;
}
}
2.1.3、MyInvocationHandler
package org.star.handler;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
@Slf4j
public class MyInvocationHandler implements InvocationHandler {
private Object obj;
public MyInvocationHandler(Object obj) {
this.obj = obj;
}
/**
* 增强逻辑
*
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 目标方法执行之前
log.info("目标方法执行之前执行,目标方法名称:{},入参:{}", method.getName(), Arrays.toString(args));
// 目标方法执行
Object result = method.invoke(obj, args);
// 目标方法执行之后
log.info("目标方法执行之后执行,result:{},obj:{}",result,obj.getClass());
return result;
}
}
2.1.4、测试类
package org.star;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.star.dao.UserDao;
import org.star.dao.impl.UserDaoImpl;
import org.star.handler.MyInvocationHandler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* Unit test for simple App.
*/
@Slf4j
public class AppTest {
@Test
public void testJDKProxy1() {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDaoImpl = new UserDaoImpl();
UserDao userDao = (UserDao) Proxy.newProxyInstance(AppTest.class.getClassLoader(),interfaces,new MyInvocationHandler(userDaoImpl));
int result = userDao.add(3,5);
log.info("result:{}",result);
}
@Test
public void testJDKProxy2() {
Class[] interfaces = {UserDao.class};
UserDao userDao = (UserDao) Proxy.newProxyInstance(AppTest.class.getClassLoader(), interfaces, new InvocationHandler() {
UserDaoImpl userDaoImpl = new UserDaoImpl();
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
// 目标方法执行之前
log.info("目标方法执行之前执行,目标方法名称:{},入参:{}", method.getName(), Arrays.toString(args));
// 目标方法执行
Object result = method.invoke(userDaoImpl, args);
// 目标方法执行之后
log.info("目标方法执行之后执行,obj:{}",userDaoImpl.getClass());
return result;
}
});
int result = userDao.add(5,5);
log.info("result:{}",result);
}
}
2.2、Cglib动态代理案例代码
2.2.1、pom
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!-- 普通maven项目中使用Sl4j注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
</dependencies>
2.2.2、Panda
package org.star.cglib;
import lombok.extern.slf4j.Slf4j;
/**
* @Author:
* @Date: 2023/8/28 14:32
* @Description:
*/
@Slf4j
public class Panda {
public void eat() {
log.info("熊猫吃竹子");
}
}
2.2.3、App
package org.star;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.star.cglib.Panda;
import java.lang.reflect.Method;
@Slf4j
public class App {
public static void main( String[] args ) {
Panda panda = new Panda();
// 创建代理类
Panda proxyPanda = (Panda) Enhancer.create(Panda.class, new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
log.info("target method before...");
Object result = method.invoke(panda, args);
log.info("target method after...");
return result;
}
});
// 通过代理类调用目标方法实现增强
proxyPanda.eat();
}
}
三、AOP术语
3.1、连接点
类里面哪些方法可以被增强,这些被增强的方法就被称为连接点。
3.2、切入点
类里面实际真正被增强的方法,称为切入点。
3.3、通知(增强)
实际增强的逻辑部分称为通知(增强),通知按照作用于目标方法的位置不同,可以分为如下5种类型。
3.3.1、前置通知
前置通知因在目标方法之前执行,故名前置通知,注解为@Before。
3.3.2、后置通知
后置通知又叫正常结束通知,即目标方法正常结束后执行,常用于做一些善后的操作,注解为@AfterReturning。
注意事项:如果方法有返回值,可以在通知中获取到方法的返回值;
3.3.3、异常通知
异常通知故名思议是指当目标方法运行期间出现异常时才会执行,注解为@AfterThrowing。
3.3.4、最终通知
最终通知有点儿类似于try..catch...finally中的finally代码块,不管目标方法运行期间是否出现异常,都会执行,用于兜底。注解为@After
3.3.5、环绕通知
环绕通知是上述四个通知的集大成者,一个通知等价于上述四个通知。注解为@Around。
注意事项:
(1)需要通过代码调用方法,在方法执行的周围进行增强;
(2)使用代码调用连接点方法:proceedingJoinPoint.proceed();
(3)环绕通知需要有返回值,此返回值就是方法调用的结果;
3.4、切面
把通知应用到切入点的过程称为切面,是动作。
四、AOP案例
4.1、基于xml配置文件实现(maven)
4.1.1、pom
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!-- 普通maven项目中使用Sl4j注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
</dependencies>
4.1.2、ATM
package org.star.component;
import lombok.extern.slf4j.Slf4j;
/**
* 目标
*/
@Slf4j
public class ATM {
public int take(int money) {
log.info("取钱方法正在执行...");
if (money == 100) {
throw new RuntimeException("自定义的异常");
}
return 10000;
}
}
4.1.3、LogAspect
package org.star.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 增强类
*/
@Slf4j
public class LogAspect {
/**
* 前置通知:方法执行之前执行
*/
public void beforeLog() {
log.info("前置通知执行...");
}
/**
* 后置通知(正常结束通知):方法正常执行结束时才会通知,出现异常不会执行
*/
public void afterReturningLog(Object result) {
log.info("后置通知(正常结束通知)执行...,返回值:{}",result);
}
/**
* 异常通知
*/
public void afterThrowingLog(Exception ex) {
log.info("异常通知(目标方法出现异常时执行)...,异常信息:{}",ex.getMessage());
}
/**
* 最终通知:不管目标方法执行期间是否有异常,都会执行
*
*/
public void afterLog() {
log.info("最终通知(不管目标方法执行期间是否有异常,都会执行)...");
}
/**
* 环绕通知
* @param joinPoint
* @return
*/
public Object aroundLog(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
log.info("around before");
result = joinPoint.proceed(); // 调用目标方法返回的值
log.info("around afterReturning");
} catch (Throwable e) {
log.info("around afterThrowing");
} finally {
log.info("around after");
}
return result;
}
}
4.1.4、applicationContext
<?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">
<!-- bean配置 -->
<bean id="atm" class="org.star.component.ATM"></bean>
<bean id="logAspect" class="org.star.aspect.LogAspect"></bean>
<!-- aop配置 -->
<aop:config>
<!--
切入点
说明:execution中的第一个参数代表返回值,*代表所有
-->
<aop:pointcut id="p" expression="execution(* org.star.component.ATM.take(..))"/>
<!-- 切面 -->
<aop:aspect ref="logAspect">
<!-- 增强作用在具体的方法上 -->
<!-- 前置通知 -->
<!--<aop:before method="beforeLog" pointcut-ref="p"></aop:before>-->
<!-- 后置通知(正常结束通知) -->
<!--<aop:after-returning method="afterReturningLog" pointcut-ref="p" returning="result"></aop:after-returning>-->
<!-- 异常通知 -->
<!--<aop:after-throwing method="afterThrowingLog" pointcut-ref="p" throwing="ex"></aop:after-throwing>-->
<!-- 最终通知 -->
<!--<aop:after method="afterLog" pointcut-ref="p"></aop:after>-->
<!-- 环绕通知 -->
<aop:around method="aroundLog" pointcut-ref="p"></aop:around>
</aop:aspect>
</aop:config>
</beans>
4.1.5、AppTest
package org.star;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.star.component.ATM;
/**
* Unit test for simple App.
*/
@Slf4j
public class AppTest {
@Test
public void aopTest() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ATM atm = context.getBean(ATM.class);
int take = atm.take(100);
log.info("aopTest take:{}",take);
}
}
4.2、基于全注解方式实现(maven)
4.2.1、pom
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!-- 普通maven项目中使用Sl4j注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
</dependencies>
4.2.2、MySpringConfig
package org.star.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"org.star"})
@EnableAspectJAutoProxy(proxyTargetClass = true) // 启用CGLB动态代理
public class MySpringConfig {
}
4.2.3、ATM
package org.star.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ATM {
public int take(int money) {
log.info("取钱方法正在执行...");
if (money == 100) {
throw new RuntimeException("自定义的异常");
}
return 10000;
}
}
4.2.4、LogAspect
package org.star.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@Aspect // 生成代理对象
public class LogAspect {
/**
* 相同的切入点进行抽取
*/
@Pointcut(value = "execution(* org.star.component.ATM.take(..))")
public void commonPoint() {
}
/**
* 前置通知:方法执行之前执行
*/
// @Before(value = "execution(* org.star.component.ATM.take(..))")
// @Before(value = "commonPoint()")
public void beforeLog() {
log.info("前置通知执行...");
}
/**
* 后置通知(正常结束通知):方法正常执行结束时才会通知,出现异常不会执行
*/
// @AfterReturning(value = "execution(* org.star.component.ATM.take(..))",returning = "result")
// @AfterReturning(value = "commonPoint()",returning = "result")
public void afterReturningLog(Object result) {
log.info("后置通知(正常结束通知)执行...,返回值:{}",result);
}
// @AfterThrowing(value = "execution(* org.star.component.ATM.take(..))",throwing = "ex")
// @AfterThrowing(value = "commonPoint()",throwing = "ex")
public void afterThrowingLog(Exception ex) {
log.info("异常通知(目标方法出现异常时执行)...,异常信息:{}",ex.getMessage());
}
/**
* 最终通知:不管目标方法执行期间是否有异常,都会执行
* 思考:目标方法正常执行结束,控制台打印的日志如下:
* 07:55:40.702 [main] INFO org.star.aspect.LogAspect - 前置通知执行...
* 07:55:40.717 [main] INFO org.star.component.ATM - 取钱方法正在执行...
* 07:55:40.717 [main] INFO org.star.aspect.LogAspect - 最终通知(不管目标方法执行期间是否有异常,都会执行)...
* 07:55:40.717 [main] INFO org.star.aspect.LogAspect - 后置通知(正常结束通知)执行...,返回值:10000
* 为什么最终通知先于后置通知执行?
* 答:后置通知是在方法执行return后执行的,是不可能修改方法的返回值的,而最终通知是在目标方法返回前执行的,即便目标方法出现抛出异常,最终通知
* 也会执行,但当抛出异常时,后置通知将不会执行。
*/
// @After(value = "execution(* org.star.component.ATM.take(..))")
// @After(value = "commonPoint()")
public void afterLog() {
log.info("最终通知(不管目标方法执行期间是否有异常,都会执行)...");
}
/**
* 环绕通知
* @param joinPoint
* @return
*/
// @Around(value = "execution(* org.star.component.ATM.take(..))")
@Around(value = "commonPoint()")
public Object aroundLog(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
log.info("around before");
result = joinPoint.proceed(); // 调用目标方法返回的值
log.info("around afterReturning result:{}",result);
} catch (Throwable e) {
log.info("around afterThrowing");
} finally {
log.info("around after");
}
return result;
}
}
4.2.5、AppTest
package org.star;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.star.component.ATM;
import org.star.config.MySpringConfig;
/**
* Unit test for simple App.
*/
@Slf4j
public class AppTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
ATM atm = context.getBean(ATM.class);
log.info("atm:{}",atm);
if (atm != null) {
atm.take(1000);
}
}
}
4.3、基于自定义注解方式实现(springboot)
4.3.1、pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
</dependencies>
4.3.2、LogAnnotation
package org.star.annotation;
import java.lang.annotation.*;
/**
* @Author: liuhaibo
* @Date: 2023/8/28 10:16
* @Description:
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
String value() default "";
}
4.3.3、UserVO
package org.star.entity.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: liuhaibo
* @Date: 2023/8/28 10:22
* @Description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserVO implements Serializable {
/**
* 编号
*/
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
}
4.3.4、UserService
package org.star.service;
import org.star.entity.vo.UserVO;
import java.util.List;
/**
* @Author : 一叶浮萍归大海
* @Date: 2023/8/28 7:55
* @Description:
*/
public interface UserService {
/**
* 查询所有用户
* @return
*/
List<UserVO> listAllUser();
/**
* 根据id查询用户
* @param id
* @return
*/
UserVO getUserById(Integer id);
/**
* 添加用户
* @param param
* @return
*/
boolean saveUser(UserVO param);
/**
* 修改用户
* @param param
* @return
*/
boolean editUser(UserVO param);
/**
* 根据id删除用户
* @param id
* @return
*/
boolean delUserById(Integer id);
}
4.3.5、UserServiceImpl
package org.star.service.impl;
import org.springframework.stereotype.Component;
import org.star.entity.vo.UserVO;
import org.star.service.UserService;
import java.util.Arrays;
import java.util.List;
/**
* @Author : 一叶浮萍归大海
* @Date: 2023/8/28 7:55
* @Description:
*/
@Component
public class UserServiceImpl implements UserService {
@Override
public List<UserVO> listAllUser() {
List<UserVO> users = Arrays.asList(
new UserVO(1, "张三", 23),
new UserVO(2, "李四", 24),
new UserVO(3, "王五", 25)
);
return users;
}
@Override
public UserVO getUserById(Integer id) {
return new UserVO(1, "张三", 23);
}
@Override
public boolean saveUser(UserVO param) {
return true;
}
@Override
public boolean editUser(UserVO param) {
return true;
}
@Override
public boolean delUserById(Integer id) {
return true;
}
}
4.3.6、UserController
package org.star.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.star.annotation.LogAnnotation;
import org.star.entity.vo.UserVO;
import org.star.service.UserService;
import java.util.List;
/**
* @Author: liuhaibo
* @Date: 2023/8/28 10:19
* @Description:
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@LogAnnotation(value = "查询所有用户")
@GetMapping("/listAllUser")
public List<UserVO> listAllUser() {
return userService.listAllUser();
}
@GetMapping("/getUserById/{id}")
public UserVO getUserById(@PathVariable("id") Integer id) {
return userService.getUserById(id);
}
@LogAnnotation(value = "添加用户")
@PostMapping("/saveUser")
public boolean saveUser(@RequestBody UserVO param) {
return userService.saveUser(param);
}
@LogAnnotation(value = "修改用户")
@PutMapping("/editUser")
public boolean editUserById(@RequestBody UserVO param) {
return userService.editUser(param);
}
@LogAnnotation(value = "根据ID删除用户")
@DeleteMapping("/delUserById/{id}")
public boolean delUserById(@PathVariable("id") Integer id) {
return userService.delUserById(id);
}
}
4.3.7、测试
启动服务,在Postman中访问接口,观察控制台的日志输出。
五、切入点表达式
5.1、概述
5.2、语法
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
5.3、案例
表达式 | 含义 |
execution(* org.star.ArithmeticCalculator.*(..)) | (1)ArithmeticCalculator接口中声明的所有方法 (2)第一个*代表任意修饰符及任意返回值 (3)第二个*代表任意方法 (4)..代表匹配任意数量、任意类型的参数。若目标类、接口与该切面类在同一个包中可以省略包名。 |
execution(public * ArithmeticCalculator.*(..)) | ArithmeticCalculator接口的所有公有方法 |
execution(public double ArithmeticCalculator.*(..)) | ArithmeticCalculator接口中返回值类型为double的方法 |
execution(public double ArithmeticCalculator.*(double,..)) | ArithmeticCalculator接口中第一个参数为double类型的方法。第二个参数".."代表匹配任意数量、任意类型的参数 |
execution(public double ArithmeticCalculator.*(double, double)) | ArithmeticCalculator接口中参数类型为double,double类型的方法 |
5.4 、高级应用
在AspectJ中,切入点表达式还可以与逻辑运算符结合起来使用
# 任意类中第一个参数为int类型的add方法或sub方法
execution (* org.star.ArithmeticCalculator.add(int,..)) || execution(* org.star.ArithmeticCalculator.sub(int,..))
# 匹配不是任意类中第一个参数为int类型的add方法
!execution (* org.star.ArithmeticCalculator.add(int,..))
六、切面的优先级
6.1、概述
如果系统定义了多个切面,如何让某些切面先运行?可以通过设置切面的优先级来改变切面的执行顺序,注解为@Order(数字),其中数字越小,优先级越高。
6.2、案例代码
6.2.1、pom
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!-- 普通maven项目中使用Sl4j注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
</dependencies>
6.2.2、ATM
package org.star.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ATM {
public int take(int money) {
log.info("取钱方法正在执行...");
if (money == 100) {
throw new RuntimeException("自定义的异常");
}
return 10000;
}
}
6.2.3、MySpringConfig
package org.star.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"org.star"})
@EnableAspectJAutoProxy(proxyTargetClass = true) // 启用CGLB动态代理
public class MySpringConfig {
}
6.2.4、LogAspect
package org.star.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @Aspect : 生成代理类
*/
@Slf4j
@Component
@Aspect
@Order(1)
public class LogAspect {
/**
* 相同的切入点进行抽取
*/
@Pointcut(value = "execution(* org.star.component.ATM.take(..))")
public void commonPoint() {
}
/**
* 前置通知:方法执行之前执行
*/
// @Before(value = "execution(* org.star.component.ATM.take(..))")
@Before(value = "commonPoint()")
public void beforeLog() {
log.info("log前置通知执行...");
}
/**
* 后置通知(正常结束通知):方法正常执行结束时才会通知,出现异常不会执行
*/
// @AfterReturning(value = "execution(* org.star.component.ATM.take(..))",returning = "result")
@AfterReturning(value = "commonPoint()",returning = "result")
public void afterReturningLog(Object result) {
log.info("log后置通知(正常结束通知)执行...,返回值:{}",result);
}
}
6.2.5、ArgsAspect
package org.star.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @Aspect : 生成代理类
*/
@Slf4j
@Component
@Aspect
@Order(2)
public class ArgsAspect {
/**
* 相同的切入点进行抽取
*/
@Pointcut(value = "execution(* org.star.component.ATM.take(..))")
public void commonPoint() {
}
/**
* 前置通知:方法执行之前执行
*/
// @Before(value = "execution(* org.star.component.ATM.take(..))")
@Before(value = "commonPoint()")
public void beforeLog() {
log.info("args前置通知执行...");
}
/**
* 后置通知(正常结束通知):方法正常执行结束时才会通知,出现异常不会执行
*/
// @AfterReturning(value = "execution(* org.star.component.ATM.take(..))",returning = "result")
@AfterReturning(value = "commonPoint()",returning = "result")
public void afterReturningLog(Object result) {
log.info("args后置通知(正常结束通知)执行...,返回值:{}",result);
}
}
6.2.6、AppTest
package org.star;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.star.component.ATM;
import org.star.config.MySpringConfig;
/**
* Unit test for simple App.
*/
@Slf4j
public class AppTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
ATM atm = context.getBean(ATM.class);
log.info("atm:{}",atm);
if (atm != null) {
atm.take(1000);
}
}
}
// 控制台打印结果
15:39:18.209 [main] INFO org.star.AppTest - atm:org.star.component.ATM@223aa2f7
15:39:18.213 [main] INFO org.star.aspect.LogAspect - log前置通知执行...
15:39:18.213 [main] INFO org.star.aspect.ArgsAspect - args前置通知执行...
15:39:18.224 [main] INFO org.star.component.ATM - 取钱方法正在执行...
15:39:18.224 [main] INFO org.star.aspect.ArgsAspect - args后置通知(正常结束通知)执行...,返回值:10000
15:39:18.224 [main] INFO org.star.aspect.LogAspect - log后置通知(正常结束通知)执行...,返回值:10000
七、使用AOP注意事项
(1)连接点方法不能是private,否则AOP不能进行增强;
(2)连接点在其他方法内部被调用时,不会被增强;