spring基础知识-AOP

1 原理

  • 面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能。

  • 底层通过动态代理来实现:

    • 第一种:有接口的情况,使用JDK动态代理:创建接口实现类的代理对象
    • 第二种:无接口的情况,使用CGLIB动态代理:创建当前类子类的代理对象

JDK动态代理

  1. 通过 java.lang.reflect.Proxy类 的 newProxyInstance方法 创建代理类。
    1. newProxyInstance方法:

image-20210801004308007

1. 参数一:类加载器
1. 参数二:所增强方法所在的类,这个类实现的接口,支持多个接口
1. 参数三:实现InvocationHandle接口,重写invoke方法来添加新的功能

接口:

1
2
3
4
public interface UserDao {
public int add(int a, int b);
public int multi(int a, int b);
}

实现类:

1
2
3
4
5
6
7
8
9
10
11
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}

@Override
public int multi(int a, int b) {
return a*b;
}
}

增强类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserDaoProxy implements InvocationHandler {
Object obj;
//通过有参构造函数将所需代理的类传过来
public UserDaoProxy(Object obj){
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("进入" + method.getName() + "方法,这是新增的代码,参数有" + Arrays.toString(args));

//执行原有的代码
Object invoke = method.invoke(obj, args);

System.out.println("方法原先的内容执行完了");

return invoke;
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {

@Test
public void test1(){

//所需代理的类实现的接口,支持多个接口
Class[] interfaces = {UserDao.class};

UserDao userDao = new UserDaoImpl();

//调用newProxyInstance方法来创建代理类
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));

int result = userDaoProxy.add(1, 2);
System.out.println(result);
}

}

image-20210801005210363

CGLIB动态代理

目标类

1
2
3
4
5
public class TargetClass {
public void doSomething() {
System.out.println("目标方法");
}
}

实现MethodInterceptor接口

1
2
3
4
5
6
7
8
9
10
11
12
13
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CustomMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("执行目标方法之前");
Object result = proxy.invokeSuper(obj, args);
System.out.println("执行目标方法之后");
return result;
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
import net.sf.cglib.proxy.Enhancer;

public class CglibDynamicProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class); //将代理类作为父类
enhancer.setCallback(new CustomMethodInterceptor());

TargetClass proxy = (TargetClass) enhancer.create();
proxy.doSomething();
}
}

结果

1
2
3
执行目标方法之前
目标方法
执行目标方法之后

2 基于AspectJ实现AOP操作

AOP相关术语

  1. 连接点:类中可以被增强的方法,称为连接点。
  2. 切入点:实际被增强的方法,称为切入点。
  3. 通知:增强的那一部分逻辑代码。通知有多种类型:
    1. 前置通知:增强部分代码在原代码前面。
    2. 后置通知:增强部分代码在原代码后面。
    3. 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。
    4. 异常通知:原代码发生异常后才会执行。
    5. 最终通知:类似与finally那一部分
  4. 切面:指把通知应用到切入点这一个动作。

切入点表达式

语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])

  1. 对 com.atguigu.dao.BookDao 类里面的 add 进行增强

    1
    execution(* com.auguigu.dao.BookDao.add(..))
  2. 对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强

    1
    execution(* com.atguigu.dao.BookDao.*(..))
  3. 对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强

    1
    execution(* com.atguigu.dao.*.* (..))

基于注解实现AOP

1
2
3
4
5
6
@Component
public class User {
public void add(){
System.out.println("User.add()");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
@Aspect //使用Aspect注解
public class UserProxy {
//前置通知
@Before(value="execution(* com.oymn.spring5.User.add(..))")
public void before(){
System.out.println("UserProxy.before()");
}

//后置通知
@AfterReturning(value="execution(* com.oymn.spring5.User.add(..))")
public void afterReturning(){
System.out.println("UserProxy.afterReturning()");
}

//最终通知
@After(value="execution(* com.oymn.spring5.User.add(..))")
public void After(){
System.out.println("UserProxy.After()");
}

//异常通知
@AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))")
public void AfterThrowing(){
System.out.println("UserProxy.AfterThrowing()");
}

//环绕通知
@Around(value="execution(* com.oymn.spring5.User.add(..))")
public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{

System.out.println("UserProxy.Around() _1");

//调用proceed方法执行原先部分的代码
proceedingJoinPoint.proceed();

System.out.println("UserProxy.Around() _2");
}
}

配置类

1
2
3
4
5
6
@Configuration
@ComponentScan(basePackages = "com.oymn.spring5")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {
}

测试

1
2
3
4
5
6
@Test
public void test2(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}

输出
image-20210801210024676

异常

1
2
3
4
public void add(){
int i = 1/0;
System.out.println("User.add()");
}

image-20210801210304774

对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut注解

1
2
3
4
5
6
7
8
9
10
@Pointcut(value="execution(* com.oymn.spring5.User.add(..))")
public void pointDemo(){

}

//前置通知
@Before(value="pointDemo()")
public void before(){
System.out.println("UserProxy.before()");
}

优先级

当有多个增强类对同一方法进行增强时,可以通过**@Order(数字值)来设置增强类的优先级,数字越小优先级越高。**

1
2
3
4
@Component
@Aspect
@Order(1)
public class PersonProxy

spring基础知识-AOP
https://baijianglai.cn/spring基础知识-AOP/a8d960500940/
作者
Lai Baijiang
发布于
2024年4月13日
许可协议