【SpringAOP】Spring AOP 底层逻辑:切点表达式与原理简明阐述

前言

🌟🌟本期讲解关于spring aop的切面表达式和自身实现原理介绍~~~

🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

🔥 你的点赞就是小编不断更新的最大动力

🎆那么废话不多说直接开整吧~~

目录

📚️1.切点表达式

🚀1.1execution表达式

🚀1.2@annotation

1.自定义注解

2.切面类

3.添加注解

[📚️2.Spring AOP原理](#📚️2.Spring AOP原理)

🚀2.1代理模式

1.静态代理(了解)

2.动态代理(八股)

[🚀2.2Spring AOP原理总结](#🚀2.2Spring AOP原理总结)

📚️3.总结

📚️1.切点表达式

我们在上一期了解到了关于通知中切点表达式execution表达式,但是没有做很详细的介绍,不仅如此在切点表达式中,有两种方式,下面来一一介绍

🚀1.1execution表达式

切点表达式的结构如下所示:

execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)

而在我们上期写的表达式中格式如下:

java 复制代码
@Around("execution(* com.example.springaop.controller.*.*(..))")

解释:

* :匹配任意字符,只匹配⼀个元素(返回类型, 包, 类名, ⽅法或者⽅法参数)

.. :匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数

上述的controller后的两个 * 代表就是每一层,括号里的 .. 就是表示任意的方法参数;这里也可以代表无参数;

举例:

execution(public String com.example.demo.controller.TestController.t1())

解释:testcontroller类下的名为t1的方法,并且这里的方法是无参的,访问修饰符可以省去;

execution(* com.example.demo.controller.TestController.t1())

解释:这里可以访问的就是没有任何限制的访问权限,匹配所有返回类型;

execution(* com.example.demo.controller.TestController.*())

解释:这里匹配所有的方法,这些方法是无参的;

execution(* com.example.demo.controller.*.*(..))

解释:这里匹配controller包下所有类的所有方法

execution(* com..TestController.*(..))

解释:这里匹配com包下的所有testcontroller类的所有方法

🚀1.2@annotation

我们在上述的简述中了解到,关于execution表达式如何作用于匹配某个方法和类,但是当存在两个类,都要进行匹配,并且里面的方法也是无规则的,那如何呢,此时就要@annotation进行操作了;

假如有以下的两个控制类:

java 复制代码
@RestController
@RequestMapping("/test")
public class TestController {
 
    @RequestMapping("/t1")
    public String test1(){
        log.info("这是t1执行");
        return "t1";
    }

    @RequestMapping("/t2")
    public String test2(){
        log.info("这是t2在执行");
        return "t2";
    }
}
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/u1")
    public String user1(){
        return "u1";
    }
   
    @RequestMapping("/u2")
    public String user2(){
        return "u2";
    }
}

那么此时我们要将第一个控制器类的第一个方法进行匹配,以及第二个控制器类的方法进行匹配,那么此时的操作就是如下所示:

1.自定义注解

代码如下所示:

java 复制代码
//定义自己的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以⽤在什么地⽅.

@Retention 指Annotation被保留的时间⻓短, 标明注解的⽣命周期

2.切面类

使⽤ @annotation 切点表达式定义切点, 只对 @MyAspect ⽣效

代码如下所示:

java 复制代码
@Aspect
@Component
@Slf4j
public class MyAnnotationAspect {
    @Before("@annotation(com.example.springaop.config.MyAspect)")
    public void doBefore(){
        log.info("before start...");
    }

    @After("@annotation(com.example.springaop.config.MyAspect)")
    public void doAfter(){
        log.info("after start...");
    }
}

这里小编设置了两种通知类,第一种是在目标方法执行前进行执行,第二种通知是在方法执行后进行执行,当然表达式里的的是我们自己注解的全限定路径,加上我们的定义注解名称;

3.添加注解

代码如下所示:

java 复制代码
    @MyAspect
    @RequestMapping("/t1")
    public String test1(){
        log.info("这是t1执行");
        return "t1";
    }

这是第一个类的第一个方法;

java 复制代码
@MyAspect
    @RequestMapping("/u2")
    public String user2(){
        return "u2";
    }

这是第二个类的第二个方法;

此时就与这两个方法进行了匹配,那么就可执行切面通知了;

📚️2.Spring AOP原理

🚀2.1代理模式

代理模式也叫委托模式定义:为其他对象提供⼀种代理以控制对这个对象的访问. 它的作⽤就是通过提供⼀个代理类, 让我们在调⽤⽬标⽅法的时候, 不再是直接对⽬标⽅法进⾏调⽤, ⽽是通过代理类间接调⽤(其实就是中介的作用)

  1. Subject: 业务接⼝类. 可以是抽象类或者接⼝(不⼀定有)

  2. RealSubject: 业务实现类. 具体的业务执⾏, 也就是被代理对象.(真正的房东)

  3. Proxy: 代理类. RealSubject的代理 (中介)

这里的代理模式分为两种

• 静态代理: 由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译, 在程序运⾏前代理类的

.class ⽂件就已经存在了.
• 动态代理: 在程序运⾏时, 运⽤反射机制动态创建⽽成

1.静态代理(了解)

代码如下:

定义一个subject业务接口;

java 复制代码
public interface SubjectHouse {
    void rentHouse();
}

让代理对象继承这个接口,并写出要代理什么

java 复制代码
public class RealSubjectHouse implements SubjectHouse{
    //真正的房东
    @Override
    public void rentHouse(){
        System.out.println("我是房东,我要出租房子");
    }
}

最后中介,进行代理

java 复制代码
public class HouseProxy implements SubjectHouse{

    private SubjectHouse subjectHouse;

    public HouseProxy(SubjectHouse subjectHouse) {
        this.subjectHouse = subjectHouse;
    }

    @Override
    public void rentHouse() {
        System.out.println("我是中介开始代理");

        subjectHouse.rentHouse();
        System.out.println("我是中介结束代理");

    }
}

解释这里使用了一个向上转型的思想,这里实际指定就是代理的对象就是realsubjecthouse(即真正的房东);

从上述代码可以看出, 我们修改接⼝(Subject)和业务实现类(RealSubject)时, 还需要修改代理类

(Proxy).

同样的, 如果有新增接⼝(Subject)和业务实现类(RealSubject), 也需要对每⼀个业务实现类新增代理类(Proxy).

2.动态代理(八股)

相⽐于静态代理来说,动态代理更加灵活.

我们不需要针对每个⽬标对象都单独创建⼀个代理对象, ⽽是把这个创建代理对象的⼯作推迟到程序运

⾏时由JVM来实现. 也就是说动态代理在程序运⾏时, 根据需要动态创建⽣成

这里的动态代理分为两种

JDK动态代理;

CGLIB动态代理;

JDK动态代理代码(了解)

java 复制代码
public class JDKInvocationHandler implements InvocationHandler {
    private Object target;//目标对象

    public JDKInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 代理对象  通过 invoke调用  目标对象的方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("我是代理, 开始代理");
        //通过反射, 调用目标对象的方法
        Object result = method.invoke(target, args);
        System.out.println("我是代理, 结束代理");
        return result;
    }
}

进行main方法调用:

java 复制代码
SubjectHouse proxy= (SubjectHouse) Proxy.newProxyInstance(
                SubjectHouse .class.getClassLoader(),
                new Class[]{SubjectHouse .class},
                new JDKInvocationHandler(target));
        proxy.rentHouse();

newProxyInstance() , 这个⽅法主要⽤来⽣成⼀个代理对象

Loader: 类加载器, ⽤于加载代理对象.

interfaces : 被代理类实现的⼀些接⼝(这个参数的定义, 也决定了JDK动态代理只能代理实现了接⼝的⼀些类)

h : 实现了 InvocationHandler 接⼝的对象,target被代理对象

CGLIB动态代理代码

添加依赖:

XML 复制代码
<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>
java 复制代码
public class CGLibMethodInterceptor implements MethodInterceptor {
    private Object target;

    public CGLibMethodInterceptor(Object target) {
        this.target = target;
    }

    /**
     * 调用代理对象的方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("我是中介, 开始代理");
        Object result = method.invoke(target, args);
        System.out.println("我是中介, 结束代理");
        return result;
    }
}

最后进行调用:

java 复制代码
  SubjectHouse houseSubject = (SubjectHouse) Enhancer.create(
                target.getClass(), new CGLibMethodInterceptor(target));
        houseSubject.rentHouse();

上述存在一个比较重要的知识点:

JDK动态代理存在一个致命的问题,即只能代理接口,不能代理类,而CGLIB既可以代理接口,又可以代理类;

🚀2.2Spring AOP原理总结

spring AOP原理,是要从源码进行解读的,但是源码过于复杂,最终的情况就是如下所示:

在源码中的代理⼯⼚有⼀个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置(这里的默认值是根据不同情况来进行定义的)

大致意思就是在 proxyTargetClass为false时,在实现接口时使用JDK代理,只实现类的情况下,使用CGLIB代理,若proxyTargetClass,为true,那么所有实现方式都使用CGLIB代理;

Spring默认使用的的proxyTargetClass为false,但是在spring boot2.x之后proxyTargetClass默认为true,使用CGLIB进行代理;

当然我们可以使用配置项进行配置

XML 复制代码
spring.aop.proxy-target-class=false

这里设置proxyTargetClass默认为false;

📚️3.总结

本期主要讲解了关于切点表达式的两种表达方式,以及Spring AOP实现原理的两种代理模式,即JDK代理,以及CGLIB代理,最后进行Spring AOP原理的总结;

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

😊😊 期待你的关注~~~

相关推荐
一颗知足的心7 分钟前
Go语言之路————指针、结构体、方法
开发语言·后端·golang
北执南念15 分钟前
项目代码生成工具
java
中国lanwp21 分钟前
springboot logback 默认加载配置文件顺序
java·spring boot·logback
苹果酱05671 小时前
【Azure Redis 缓存】在Azure Redis中,如何限制只允许Azure App Service访问?
java·vue.js·spring boot·mysql·课程设计
Java致死1 小时前
单例设计模式
java·单例模式·设计模式
胡子发芽1 小时前
请详细解释Java中的线程池(ThreadPoolExecutor)的工作原理,并说明如何自定义线程池的拒绝策略
java
沫夕残雪1 小时前
Tomcat的安装与配置
java·tomcat
Rabbb2 小时前
C# JSON属性排序、比较 Newtonsoft.Json
后端
蓝易云2 小时前
在Linux、CentOS7中设置shell脚本开机自启动服务
前端·后端·centos
胡子发芽2 小时前
请解释Java中的NIO(New I/O)与传统I/O的区别,并说明NIO中的关键组件及其作用
java