Spring AOP

1. AOP概述

1.1 AOP是什么

AOP(Aspect Oriented Programming,面向切面编程),首先面向切面是一种思想,它看似与面向对象相对,但实则为面向对象的延续。

面向对象自问世以来,因其贴合现实生活,对编程人员极为友好,广受业内喜爱。 但单纯的面向对象编程,在一些场景下,好像不如现实生活中简单自然。 在OOP(面向对象编程)中,类之间的关系如下图:

img

这其中清晰地展示了类与类之间的父子关系,却没办法表示如下图所示的同级关系。

img

AOP的出现便是为了弥补此类需求。

AOP作为一种编程思想,其应用场景和具体的技术实现并不是固定的,其实Spring MVC中的拦截器就是AOP的一个具体实现。

1.2 AOP术语

  • 关于Spring AOP,需要先掌握一组相关术语,以方便后面的学习和理解。

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。

  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。

    ABCD 四个方法分别就是连接点,我们要对ABC进行拦截定义,那么ABC整体就是切入点

  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,

    最终通知,环绕通知。

    你拦截到方法需要校验,校验这个动作就是增强

    比如在ABC每个方法前都加一段代码,那么把这段代码我们就加在过滤器或者拦截器中,写一段即可, 这也属于方法的增强,

  • Target(目标对象):代理的目标对象

    ABCD所属于类型的对象就是Target

  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。

    A方法所在对象去创建一个代理对象A1,并把增强代码织入到A1中,这个过程称之为织入。

  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。

    A1就是代理类,A方法就是目标

  • Aspect(切面):是切入点和通知的结合。

    A方法所在对象去创建一个代理对象A1,

    B方法所在对象去创建一个代理对象B1,

    C方法所在对象去创建一个代理对象C1,

    那么A1 B1 C1三个代理类整体就是个切面。

  • 参考图解

image-20230308233330028

1.3.Spring AOP原理(代理模式)

Spring AOP的原理是动态代理,通过对目标类的代理,完成功能代码的织入。

生活中的代理:律师,医生

代码中的代理:设计一个算法,验证它的耗时。

代理可分为静态代理和动态代理

  • 静态代理,可在编译阶段对目标类织入增强代码;代码实现

    • 典型有JDK静态代理和AspectJ

  • 动态代理,在程序运行过程中,动态地为目标类织入增强代码。反射实现

    • 典型有JDK动态代理和CGLib

Spring AOP采用JDK动态代理+CGLib的方式,使用二者结合体,原因是两种技术都有一点缺陷:

  • JDK动态代理 目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。

  • CGLib动态代理 继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final。

静态代理
jdk静态代理:

代理类在编译期间就已经确定了,代理类是需要开发者自己定义,可能会存在多个代理类

// 第一种 继承
// 目标类
public class Service1 {
    public String test(String name) {
        System.out.println("Hello   " + name);
        return "Hello   " + name;
    }
}

// 代理类
public class Service1Proxy extends Service1 {
    @Override
    public String test(String name) {
        System.out.println("Proxy....");
        return super.test(name);
    }
}

// 测试类
public class App {
    public static void main(String[] args) {
//      Service1 service1 = new Service1();
//      service1.test("AOP");
        Service1Proxy service1Proxy = new Service1Proxy();
        service1Proxy.test("AOP");

    }
}

// 第二种,接口实现
// 接口
public interface Service2 {
    String test(String name);
}

// 目标类
public class Service2Impl implements Service2 {

    @Override
    public String test(String name) {
        System.out.println("Hello  " + name);
        return "Hello" + name;
    }
}

// 代理类
public class Service2Proxy implements Service2 {
    Service2 service2 = new Service2Impl();

    @Override
    public String test(String name) {
        System.out.println("Proxy....");
        return service2.test(name);
    }
}

// 测试类
//      Service2 service2 = new Service2Impl();
//      service2.test("AOP");
        Service2Proxy service2Proxy = new Service2Proxy();
        service2Proxy.test("AOP");
动态代理
Jdk动态代理:

具体代理类型需要在运行期间确定,开发者不需要自己实现代理类

interface UserService {
    void addUser(String username, String password);
}

interface DeptService {
    void addDept(String deptName);
}

class UserServiceImpl implements UserService {

    @Override
    public void addUser(String username, String password) {
        System.out.println(username + "用户添加成功");
    }
}

class DeptServiceImpl implements DeptService {

    @Override
    public void addDept(String username) {
        System.out.println(username + "部门添加成功");
    }

}

class JdkDynamicProxy implements InvocationHandler {

    private Object object;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志1");
        Object invoke = method.invoke(object, args);
        System.out.println("日志2");
        return invoke;
    }

    public Object newProxyInstance(Object obj) {
        this.object = obj;
        Object o = 
        Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
        return o;
    }
}

class Main2 {
    public static void main(String[] args) {
        JdkDynamicProxy u = new JdkDynamicProxy();

        UserService o = (UserService) u.newProxyInstance(new UserServiceImpl());
        o.addUser("ls", "1234");

        DeptService o1 = (DeptService) u.newProxyInstance(new DeptServiceImpl());
        o1.addDept("销售部");
    }
}
//缺点:
//JDK动态代理: 目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
//CGLib动态代理:继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final。

 CGLIB动态代理

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>
package com.codingtuture.dymicproxy.cglibdynamicProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public interface UseService {
    void addUser(String username, String password);
}

class UseServiceImpl implements UseService {

    @Override
    public void addUser(String username, String password) {
        System.out.println(username + "用户添加成功");
    }
}

class CglibProxy implements MethodInterceptor {

    public Enhancer enhancer = new Enhancer();

    public Object getDaoBean(Class cls) {
        enhancer.setSuperclass(cls);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
         System.out.println("日志1");
         Object o1 = methodProxy.invokeSuper(o, objects);
         System.out.println("日志2");
         return o1;
    }
}

class Main3 {
    public static void main(String[] args) {
        CglibProxy u = new CglibProxy();
        UseService daoBean = (UseService) u.getDaoBean(UseServiceImpl.class);
        daoBean.addUser("qqq", "1212");
    }
}
//缺点:
//JDK动态代理: 目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
//CGLib动态代理:继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final。

Spring AOP采用JDK动态代理+CGLib的方式,原因是两种技术都有一点缺陷:

  • JDK动态代理是通过实现目标类的接口来进行代理,因此只能代理实现了接口的类;

  • CGLib通过继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final的。

鉴于JDK动态代理不需要额外jar包,是JDK内部技术,稳定性要比第三方强,所以Spring的策略是默认采用JDK动态代理,如果目标类没有实现接口,则采用CGLib。

Spring还引入了AspectJ对于AOP的配置方式,注意,仅仅是引入了配置方式,并未采用AspectJ的AOP实现。

2. Spring AOP的使用

创建springboot项目

Spring引入了AspectJ对于AOP的配置方式,注意,仅仅是引入了配置方式,并未采用AspectJ的AOP实现。

2.1 依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 切面 Aspect(切面):是切入点和通知的结合。

1.切入点

2.通知

package com.codingfuture.demoaop.aop;

/**
 * @author Petrel
 * @create 2022-11-04 10:46 AM
 */
public interface DemoService {
    void hello ();
}



package com.codingfuture.demoaop.aop;
import org.springframework.stereotype.Component;

/**
 * @author Petrel
 * @create 2022-11-04 10:46 AM
 */
@Component
public class DemoServiceImpl implements DemoService{
    @Override
    public void hello() {
        System.out.println("hello...");
        // int i = 10 / 0
    }
}


package com.codingfuture.demoaop.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author Petrel
 * @create 2022-11-04 10:48 AM
 */
// 切面类
@Component
@Aspect
public class DemoAspect {
     @Before("(* *.*.*.*.*.*(..))")
    // Before增强方式
    @Before("execution(* com.codingfuture.demoaop.aop.*.*(..))")
    // 增强代码
    public void  testBefore () {
        System.out.println("before.....");
    }
}

// test 测试
package com.codingfuture.demoaop;

import com.codingfuture.demoaop.aop.DemoService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoaopApplicationTests {
    @Autowired
    private DemoService demoService;

    @Test
    void contextLoads() {
        demoService.hello();
        System.out.println(demoService.getClass());
    }

}
2.3 切点表达式
pointcut = "execution(* com.codingfuture.controller.*.*(..))"
execution(* com.codingfuture.controller.*.*(..))"
整个表达式可以分为五个部分
 1、execution():表达式主体。
 2、第一个*号:表示返回类型,*号表示所有的类型。
 3、包名:表示需要拦截的包名,后面的两个句点分别表示当前包和当前包的所有子包,com.codingfuture包、子孙包
 4、第二个*号:表示类名,*号表示所有的类。
 5、*(..) :第三个*表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

<!-- 【拦截所有public方法】 -->
"execution(public * *(..))"

<!-- 【拦截所有save开头的方法 】 -->
"execution(* save*(..))"

<!-- 【拦截指定类的指定方法】 -->
"execution(public * com.codingfuture.pointcut.OrderDao.save(..))"

<!-- 【拦截指定类的所有方法】 -->
"execution(* com.codingfuture.pointcut.UserDao.*(..))"

<!-- 【拦截指定包,以及其子包下所有类的所有方法】 -->
"execution(* com..*.*(..))"

<!-- 【多个表达式 与或非】 -->
"execution(* com.codingfuture.pointcut.UserDao.save()) || execution(*com.codingfuture.pointcut.OrderDao.save())"
"execution(* com.codingfuture.pointcut.UserDao.save()) or execution(*com.codingfuture.pointcut.OrderDao.save())"
"execution(* com.codingfuture.pointcut.UserDao.save()) && execution(*com.codingfuture.pointcut.OrderDao.save())"
"execution(* com.codingfuture.pointcut.UserDao.save()) and execution(* com.codingfuture.pointcut.OrderDao.save())"
"!execution(* com.codingfuture.pointcut.OrderDao.save())"
"not execution(* com.codingfuture.pointcut.OrderDao.save())"

上方的写法主要是针对aspectJ框架支持的切面的配置。当前AOP的配置基本上都用上面这种形式的配置。

2.4 JDK动态代理和CGLib
  • Spring默认采用JDK动态代理,如果目标类中的切入点不是基于接口实现的,那么则采用CGLib进行代理。

  • Spring Boot默认采用CGLib进行代理,想要切换为JDK动态代理,需要配置。

  • spring.aop.proxy-target-class=false

相关推荐

  1. SpringAOP的实现原理

    2024-03-24 13:52:03       31 阅读
  2. 一个简易的SpringAOP实例

    2024-03-24 13:52:03       40 阅读
  3. 浅谈SpringAOP实现原理

    2024-03-24 13:52:03       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-24 13:52:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-24 13:52:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-24 13:52:03       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-24 13:52:03       20 阅读

热门阅读

  1. C#面:什么是 NuGet

    2024-03-24 13:52:03       19 阅读
  2. 富格林:利用可信技巧租阻止暗箱陷阱

    2024-03-24 13:52:03       20 阅读
  3. Kafka系列之:Connect 中的错误报告

    2024-03-24 13:52:03       22 阅读
  4. 【MySQL】覆盖索引

    2024-03-24 13:52:03       19 阅读
  5. 【LeetCode-394.字符串解码】

    2024-03-24 13:52:03       16 阅读
  6. 24.3.24 《CLR via C#》 笔记10

    2024-03-24 13:52:03       15 阅读
  7. 优化 - 数据结构

    2024-03-24 13:52:03       19 阅读
  8. 100268. 最长公共后缀查询(字典树查询)

    2024-03-24 13:52:03       17 阅读
  9. 重新了解一下之前的單對象變化問題

    2024-03-24 13:52:03       18 阅读
  10. Bom,事件对象

    2024-03-24 13:52:03       20 阅读
  11. extern c 和extern c++

    2024-03-24 13:52:03       17 阅读