Spring AOP

发布时间:2023年12月21日

目录

1. AOP概述

1.1 AOP是什么

1.2 AOP术语

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

静态代理

jdk静态代理:

动态代理

Jdk动态代理:

CGLIB动态代理

2. Spring AOP的使用

2.1 依赖

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

2.3 切点表达式

2.4 JDK动态代理和CGLib

?


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.whitecamellia.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.whitecamellia.demoaop.aop;
?
/**
 * @author Petrel
 * @create 2022-11-04 10:46 AM
 */
public interface DemoService {
 ? ?void hello ();
}
?
?
?
package com.whitecamellia.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.whitecamellia.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("execution(* com.whitecamellia.demoaop.aop.*.*(..))")
 ? ?// 增强代码
 ? ?public void ?testBefore () {
 ? ? ? ?System.out.println("before.....");
 ?  }
}
?
// test 测试
package com.whitecamellia.demoaop;
?
import com.whitecamellia.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());
 ?  }
?
}
?

运行结果:

before.....
hello...
class com.whitecamellia.demoaop.aop.DemoServiceImpl$$EnhancerBySpringCGLIB$$36f6325b
 // 程序运行中通过代理动态生成的代理类

完整测试代码

package com.whitecamellia.demoaop.aop;
?
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
?
/**
 * @author Petrel
 * @create 2022-11-04 10:48 AM
 */
// 切面类
@Component
@Aspect
public class DemoAspect {
?
 ? ?@Pointcut("execution(* com.whitecamellia.demoaop.aop.*.*(..))")
 ? ?public ?void pointCut () {}
 ? ?// 前置通知
 ? ?// Before增强方式
 ? ?@Before("pointCut ()")
 ? ?// 增强代码
 ? ?public void ?testBefore () {
 ? ? ? ?System.out.println("before.....");
 ?  }
 ? ?// 后置通知
 ? ?@After("pointCut ()")
 ? ?public void ?testAfter () {
 ? ? ? ?System.out.println("after.....");
 ?  }
?
 ? ?// 环绕通知
 ? ?@Around("pointCut ()")
 ? ?public Object ?testAround (ProceedingJoinPoint joinPoint)  {
 ? ? ? ?System.out.println("before.....");
 ? ? ? ?Object proceed = null;
 ? ? ? ?try {
 ? ? ? ? ? ?proceed = joinPoint.proceed();
 ? ? ?  } catch (Throwable e) {
 ? ? ? ? ? ?e.printStackTrace();
 ? ? ?  }
 ? ? ? ?System.out.println("after.....");
 ? ? ? ?return proceed;
 ?  }
 ? ?// 异常通知
 ? ?@AfterThrowing("pointCut ()")
 ? ?public void testAfterThrowing () {
 ? ? ? ?System.out.println("afterThrowing.....");
 ?  }
 ? ?// 最终通知
 ? ?@AfterReturning(("pointCut ()"))
 ? ?public void ?testAfterReturning () {
 ? ? ? ?System.out.println("afterReturning.....");
 ?  }
}
?
2.3 切点表达式

可参考如下实例:

pointcut = "execution(* com.whitecamellia.controller.*.*(..))"
execution(* com.whitecamellia.controller.*.*(..))"
整个表达式可以分为五个部分
 1、execution():表达式主体。
 2、第一个*号:表示返回类型,*号表示所有的类型。
 3、包名:表示需要拦截的包名,后面的两个句点分别表示当前包和当前包的所有子包,com.whitecamellia包、子孙包
 4、第二个*号:表示类名,*号表示所有的类。
 5、*(..) :第三个*表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
?
<!-- 【拦截所有public方法】 -->
"execution(public * *(..))"
?
<!-- 【拦截所有save开头的方法 】 -->
"execution(* save*(..))"
?
<!-- 【拦截指定类的指定方法】 -->
"execution(public * com.whitecamellia.pointcut.OrderDao.save(..))"
?
<!-- 【拦截指定类的所有方法】 -->
"execution(* com.whitecamellia.pointcut.UserDao.*(..))"
?
<!-- 【拦截指定包,以及其子包下所有类的所有方法】 -->
"execution(* com..*.*(..))"
?
<!-- 【多个表达式 与或非】 -->
"execution(* com.whitecamellia.pointcut.UserDao.save()) || execution(*com.whitecamellia.pointcut.OrderDao.save())"
"execution(* com.whitecamellia.pointcut.UserDao.save()) or execution(*com.whitecamellia.pointcut.OrderDao.save())"
"execution(* com.whitecamellia.pointcut.UserDao.save()) && execution(*com.whitecamellia.pointcut.OrderDao.save())"
"execution(* com.whitecamellia.pointcut.UserDao.save()) and execution(* com.whitecamellia.pointcut.OrderDao.save())"
"!execution(* com.whitecamellia.pointcut.OrderDao.save())"
"not execution(* com.whitecamellia.pointcut.OrderDao.save())"

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

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

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

    spring.aop.proxy-target-class=false

?

文章来源:https://blog.csdn.net/2301_77351901/article/details/135117380
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。