AOP的中文名称是面向切面编程或者面向方面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
不通过修改源代码的方式,在主干功能里面添加新功能。
AOP底层使用动态代理技术。有两种情况的动态代理:
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);
}
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;
}
}
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;
}
}
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);
}
}
cglib
cglib
3.1
aopalliance
aopalliance
1.0
org.aspectj
aspectjweaver
1.9.19
org.springframework
spring-aop
5.2.5.RELEASE
commons-logging
commons-logging
1.1.1
com.alibaba
druid
1.2.16
org.springframework
spring-beans
5.2.5.RELEASE
org.springframework
spring-context
5.2.5.RELEASE
org.springframework
spring-core
5.2.5.RELEASE
org.springframework
spring-expression
5.2.5.RELEASE
junit
junit
4.13.2
org.projectlombok
lombok
1.18.22
org.slf4j
slf4j-api
1.7.32
ch.qos.logback
logback-classic
1.2.10
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("熊猫吃竹子");
}
}
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();
}
}
类里面哪些方法可以被增强,这些被增强的方法就被称为连接点。
类里面实际真正被增强的方法,称为切入点。
实际增强的逻辑部分称为通知(增强),通知按照作用于目标方法的位置不同,可以分为如下5种类型。
前置通知因在目标方法之前执行,故名前置通知,注解为@Before。
后置通知又叫正常结束通知,即目标方法正常结束后执行,常用于做一些善后的操作,注解为@AfterReturning。
注意事项:如果方法有返回值,可以在通知中获取到方法的返回值;
异常通知故名思议是指当目标方法运行期间出现异常时才会执行,注解为@AfterThrowing。
最终通知有点儿类似于try..catch...finally中的finally代码块,不管目标方法运行期间是否出现异常,都会执行,用于兜底。注解为@After
环绕通知是上述四个通知的集大成者,一个通知等价于上述四个通知。注解为@Around。
注意事项:
(1)需要通过代码调用方法,在方法执行的周围进行增强;
(2)使用代码调用连接点方法:proceedingJoinPoint.proceed();
(3)环绕通知需要有返回值,此返回值就是方法调用的结果;
把通知应用到切入点的过程称为切面,是动作。
cglib
cglib
3.1
aopalliance
aopalliance
1.0
org.aspectj
aspectjweaver
1.9.19
org.springframework
spring-aop
5.2.5.RELEASE
commons-logging
commons-logging
1.1.1
com.alibaba
druid
1.2.16
org.springframework
spring-beans
5.2.5.RELEASE
org.springframework
spring-context
5.2.5.RELEASE
org.springframework
spring-core
5.2.5.RELEASE
org.springframework
spring-expression
5.2.5.RELEASE
junit
junit
4.13.2
com.alibaba
fastjson
1.2.76
org.projectlombok
lombok
1.18.22
org.slf4j
slf4j-api
1.7.32
ch.qos.logback
logback-classic
1.2.10
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;
}
}
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;
}
}
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);
}
}
cglib
cglib
3.1
aopalliance
aopalliance
1.0
org.aspectj
aspectjweaver
1.9.19
org.springframework
spring-aop
5.2.5.RELEASE
commons-logging
commons-logging
1.1.1
com.alibaba
druid
1.2.16
org.springframework
spring-beans
5.2.5.RELEASE
org.springframework
spring-context
5.2.5.RELEASE
org.springframework
spring-core
5.2.5.RELEASE
org.springframework
spring-expression
5.2.5.RELEASE
junit
junit
4.13.2
com.alibaba
fastjson
1.2.76
org.projectlombok
lombok
1.18.22
org.slf4j
slf4j-api
1.7.32
ch.qos.logback
logback-classic
1.2.10
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 {
}
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;
}
}
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;
}
}
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);
}
}
}
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-aop
org.projectlombok
lombok
true
org.apache.commons
commons-collections4
4.4
org.apache.commons
commons-lang3
com.alibaba
fastjson
2.0.6
cn.hutool
hutool-all
5.7.22
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 "";
}
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;
}
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 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);
}
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 listAllUser() {
List 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;
}
}
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 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);
}
}
启动服务,在Postman中访问接口,观察控制台的日志输出。
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
表达式 | 含义 |
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类型的方法 |
在AspectJ中,切入点表达式还可以与逻辑运算符结合起来使用
# 任意类中第一个参数为int类型的add方法或sub方法
execution (* org.star.ArithmeticCalculator.add(int,..)) || execution(* org.star.ArithmeticCalculator.sub(int,..))
# 匹配不是任意类中第一个参数为int类型的add方法
!execution (* org.star.ArithmeticCalculator.add(int,..))
如果系统定义了多个切面,如何让某些切面先运行?可以通过设置切面的优先级来改变切面的执行顺序,注解为@Order(数字),其中数字越小,优先级越高。
cglib
cglib
3.1
aopalliance
aopalliance
1.0
org.aspectj
aspectjweaver
1.9.19
org.springframework
spring-aop
5.2.5.RELEASE
commons-logging
commons-logging
1.1.1
com.alibaba
druid
1.2.16
org.springframework
spring-beans
5.2.5.RELEASE
org.springframework
spring-context
5.2.5.RELEASE
org.springframework
spring-core
5.2.5.RELEASE
org.springframework
spring-expression
5.2.5.RELEASE
junit
junit
4.13.2
com.alibaba
fastjson
1.2.76
org.projectlombok
lombok
1.18.22
org.slf4j
slf4j-api
1.7.32
ch.qos.logback
logback-classic
1.2.10
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;
}
}
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 {
}
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);
}
}
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);
}
}
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
(1)连接点方法不能是private,否则AOP不能进行增强;
(2)连接点在其他方法内部被调用时,不会被增强;