Spring AOP

目录

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(面向对象编程)中,类之间的关系如下图:

Spring AOP_第1张图片

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

Spring AOP_第2张图片

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三个代理类整体就是个切面。

  • 参考图解

Spring AOP_第3张图片

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动态代理
 
    cglib
    cglib
    3.1
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 依赖

            org.springframework.boot
            spring-boot-starter-aop
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、*(..) :第三个*表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
​

"execution(public * *(..))"
​

"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

 

你可能感兴趣的:(spring,java,后端)