Spring AOP 与代理的概念与使用

一、AOP 的基本概念

1.1 什么是 AOP

Aspect Oriented Programming,面向切面编程。

就跟我们说 OOP 是面向对象一样,AOP 是面向切面的。切面是分散在应用中的一个标准代码或功能。切面通常与实际的业务逻辑不同(例如,事务管理)。每个切面专注于一个特定的环切功能。

这里的切面呢,可以理解为横切。比如在所有的 DAO 层方法上加上一个同样的切面,功能是记录日志;又或者在某个接口上应用一个切面,作用是检查权限。

AOP 是基于代理来实现的。而代理又分为静态代理和动态代理。两者的区别在于代理类于何时生成。

下面我们讲讲代理是怎么回事?

1.2 代理与 Spring AOP

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

  • 静态代理:代理类在编译阶段生成,程序运行前就存在。包括:AspectJ 静态代理、JDK 静态代理
  • 动态代理:代理类在程序运行时创建。包括:JDK 动态代理、CGLib 动态代理

Spring AOP 原理:

  • JDK Proxy:interface based
  • CGLib Proxy: class based

Spring AOP 中默认使用 JDK 动态代理,通过反射获取被代理的类,这个类必须实现一个接口。如果目标类没有实现接口,就会默认使用 CGLIB Proxy 来动态生成代理目标类,后者是被代理类的子类。

可以通过获取代理对象并打印的方式来查看其类型(JDK Proxy 下是 com.sun.prxy, CGlib 下是子类.

AspectJ: 用特定的编译器和语法,在编译时增强,实现了静态代理技术。

1.3 Spring AOP 与 AspectJ 的区别

AspectJ 是一套完整的 AOP 解决方案,而 Spring AOP 并不是 —— 它只是在 Spring 框架下满足其使用要求的一个解决方法,比如 Spring AOP 仅支持对方法使用切面。

二、静态代理

2.1 AspectJ 静态代理

基于特殊的编译器和语法。这里不多介绍了。

IDEA 下编译 AspectJ 可以参考这篇:https://blog.csdn.net/gavin_john/article/details/80156963

2.2 JDK 静态代理

实际上是利用实现一个具体的代理类来调用业务类。代理类持有了一个业务类的引用。

更概况地说,JDK 静态代理体现的是一种设计模式。

缺点很明显,代码冗余,难以维护。

这里以 借书 和 还书 这两个行为来作为一个示例:

编写一个 BookService 接口:

public interface BookService {

    boolean borrow(String id, String userName);

    boolean reBack(String id, String userName);
}

然后实现这个接口:

public class BookServiceImpl implements BookService {

    @Override
    public boolean borrow(String id, String userName) {
        System.out.println(userName + " 借书:" + id);
        return true;
    }

    @Override
    public boolean reBack(String id, String userName) {
        System.out.println(userName + " 还书:" + id);
        return true;
    }
}

下面我们来编写 BookService 的代理类:

public class BookProxy implements BookService {

    private BookServiceImpl bookService;

    public BookProxy(BookServiceImpl bookService) {
        this.bookService = bookService;
    }

    @Override
    public boolean borrow(String id, String userName) {
        boolean res = false;
        if (check()) {
            res = bookService.borrow(id, userName);
        }
        addLog();
        return res;
    }

    @Override
    public boolean reBack(String id, String userName) {
        boolean res = false;
        if (check()) {
            res = bookService.reBack(id, userName);
        }
        addLog();
        return res;
    }

    //
    private boolean check() {
        System.out.println("检查权限");
        return true;
    }

    private void addLog() {
        System.out.println("操作完成");
    }
}

编写一个测试类:

public class MainTest {

    public static void main(String[] args) {
        BookProxy proxy = new BookProxy(new BookServiceImpl());
        proxy.borrow("123", "eknown");
        proxy.reBack("234", "java");
    }
}

这里我们可以看到,JDK 静态代理就是说在原来的实现类上套一层 代理。它好像是体现了代理模式,但实际上并没有带来太多的好处。代码相当冗余,也不利于维护。

真正体现代理模式好处的还是动态代理,下面我们来看看动态代理的原理。

三、动态代理

动态代理是程序运行时,由 JVM 根据反射等机制动态生成代理类的。

也就是说,程序运行前,我们仅仅定义了代理的规则,而不知道代理类具体长什么样,这不像上面的静态代理里,我们完整地定义了代理对象。

3.1 JDK 动态代理

JDK 动态代理是基于接口的。

我们可以通过实现 InvocationHandler 接口来手动创建一个 JDK 代理类。

首先需要定义一个接口,让业务类和代理类都实现这个接口。

然后编写一个 InvocationHandler 接口的实现类:

public class BookProxy implements InvocationHandler {

    // 被该代理类处理的业务类
    private BookService bookService;

    public BookProxy(BookService bookService) {
        this.bookService = bookService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = null;
        if (check()) {
            // 调用实际的 method,参数是 接口 + 参数
            res = method.invoke(bookService, args);
        }
        addLog();
        return res;
    }
    
    private boolean check() {
        System.out.println("检查权限");
        return true;
    }

    private void addLog() {
        System.out.println("操作完成");
    }
}

测试:

public class MainTest {

    public static void main(String[] args) {
        // 创建被代理的实际业务类
        BookServiceImpl bookServiceImpl = new BookServiceImpl();

        ClassLoader classLoader = bookServiceImpl.getClass().getClassLoader();
        // 获取所有的接口方法
        Class[] interfaces = bookServiceImpl.getClass().getInterfaces();

        // 构造 Handler
        InvocationHandler invocationHandler = new BookProxy(bookServiceImpl);

        // 创建代理
        Object obj = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        BookService bookService = (BookService) obj;
        bookService.borrow("abc", "eknown");
        bookService.reBack("c23", "py");
    }
}

3.2 CGLIB 动态代理

CGLIB 代理的原理是:让代理类继承业务类(也就自动拥有了业务类的所有非 final 的 public 方法)

我们这里手动编写一个 CGLIB 的代理试试看。

首先我们有一个 BookServiceImpl 业务类,这个业务类可以实现接口,也可以就是单纯的一个业务类。

然后我们定义一个 BookCglibProxy 类:

public class BookCglibProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        check();
        // 调用实际的 method
        Object obj = methodProxy.invokeSuper(o, objects);
        addLog();
        return obj;
    }

    private boolean check() {
        System.out.println("检查权限");
        return true;
    }

    private void addLog() {
        System.out.println("操作完成");
    }
}

测试类:

public class CglibTest {
    public static void main(String[] args) {
        BookServiceImpl bookServiceImpl = new BookServiceImpl();

        BookCglibProxy proxy = new BookCglibProxy();

        // cjlib 中的增强器,用于创建动态代理(被代理类的子类)
        Enhancer enhancer = new Enhancer();
        // 设置要被代理的类
        enhancer.setSuperclass(bookServiceImpl.getClass());
        // 设置回调
        enhancer.setCallback(proxy);

        // 强转成父类
        BookServiceImpl proxyResult = (BookServiceImpl) enhancer.create();
        proxyResult.borrow("12333", "ye");
        proxyResult.reBack("123", "fe");
    }
}

在第一节我们提到过 Spring AOP 是基于 JDK 动态代理和 CGLIB 动态代理的。下面我们来 Spring AOP 的一些基本案例。

四、Spring AOP 实例

AOP 中一些概念词汇,通过这些词汇,我们可以对 AOP 有更高一层的抽象。

  • Aspect - 切面,分散在应用中的一个标准代码或功能。切面通常与实际的业务逻辑不同(例如,事务管理)。每个切面专注于一个特定的环切功能。
  • Joinpoint - 连接点,是程序执行过程中的特定点,比如方法执行、构造器调用、字段赋值
  • Advice - 通知,切面在某个连接点采取的操作。Advice 有 5 种类型。
  • Pointcut - 切入点,一个匹配连接点的正则表达式。每当连接点匹配了一个切入点时,一个特定的通知就会被执行。
  • Weaving - 织入,指的是将切面和目标对象连接起来以创建代理对象的过程。

Spring AOP 有两种实现方式:基于 XML 或基于注解。更流行、更方便的是后者。(阿 sir,不会还有人用 XML 来做 Bean 的配置文件吧?)

4.1 基于 XML 的实例

首先定义一下接口和实现类(没有注解的!)。再编写一个代理类:

这里的代理类方法以 JoinPoint 为参数即可:

public class BookAspect {

    public void checkUser(JoinPoint point) {
        System.out.println("-----before-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("检查用户权限...");
    }

    public void saveLog(JoinPoint point) {
        System.out.println("-----after-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("请求完毕,记录日志...");
    }
}

然后编写 Spring 的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

    <!-- 定义 bean -->
    <bean id="bookService" class="com.example.springaopdemo.basicxml.BookServiceImpl" />
    <bean id="bookAspect" class="com.example.springaopdemo.basicxml.BookAspect" />

    <aop:config>
        <!-- 这是定义一个切面,切面是切点和通知的集合-->
        <aop:aspect id="do" ref="bookAspect">
            <!-- 定义切点 ,后面是 expression 语言,表示包括该接口中定义的所有方法都会被执行 -->
            <aop:pointcut id="point" expression="execution(* com.example.springaopdemo.basicxml.BookService.*(..))" />
            <!-- 定义通知 -->
            <aop:before method="checkUser" pointcut-ref="point" />
            <aop:after method="saveLog" pointcut-ref="point" />
        </aop:aspect>
    </aop:config>
</beans>

运行测试:

public class AopXMLTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("SpringAop.xml");

        BookService bookService = context.getBean("bookService", BookService.class);
        bookService.borrow("123", "eknown");
        bookService.reback("123", "eknown");
    }
}

基于 XML 配置的 Spring 现在已经很少使用了。下面我们来看看如何基于注解使用 Spring AOP

4.2 基于注解的实例

这里以一个使用 SpringBoot 框架的 Web 项目作为简单的实例。

首先创建一个 SpringBoot 项目,写好 Controller、Service、DAO 层的基本类。(示例源码中没有使用 Mybatis 等持久层框架,而是用 Map 来模拟数据的存取)

下面我们针对 UserService 接口类,添加切面。

@Aspect
@Component
public class UserAspect {

    @Before(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")
    public void checkUser(JoinPoint point) {
        System.out.println("-----before-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("检查..." + point);
    }

    @After(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")
    public void saveLog(JoinPoint point) {
        System.out.println("-----after-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }

        // 这里可以使用 point.getTarget() 获取到切面对应的 bean
        //Object target = point.getTarget();
        //UserService userService = (UserService) target;
        //List<User> userList = userService.findAll();

        System.out.println("请求完毕,记录日志..." + point);
    }

    @Around(value = "execution(* com.example.springaopdemo.boot.UserService.save(..))")
    public Object saveAround(ProceedingJoinPoint point) {
        System.out.println("around-before");
        Object obj = null;
        try {
            obj = point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("around-after");
        return obj;
    }

}

示例中使用了 @Before/@After/@Aroud 三个注解,value 中使用切点表达式,分别匹配了 UserService 接口的所有方法和单个 save 方法。

我们还可以通过切点表达式匹配自定义的注解,比如实现一个 UserMonitor 注解,然后定义其切点方法:

public @interface UserMonitor {

    String value() default "";

    int roleLimit() default 0;

}

切点:

@Around("@annotation(com.example.springaopdemo.boot.UserMonitor)")
    public Object userRolePointCut(ProceedingJoinPoint point) {
        System.out.println("检查用户权限...");
    
       // 获取参数
        Object[] args = point.getArgs();
        Class<?>[] argTypes = new Class[point.getArgs().length];
        for (int i = 0; i < args.length; i++) {
            argTypes[i] = args[i].getClass();
        }
      
        // 获取方法
        Method method = null;
        try {
            method = point.getTarget().getClass()
                    .getMethod(point.getSignature().getName(), argTypes);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }

        // 获取方法上的该注解,之后可以根据注解中的值进行一些操作,比如判定是否具有权限
        UserMonitor monitor = method.getAnnotation(UserMonitor.class);
        System.out.println(monitor);
        Object obj = null;
        try {
            obj = point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }

以上就是Spring AOP 与代理的概念与使用的详细内容,更多关于Spring AOP 与代理的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring基于ProxyFactoryBean创建AOP代理

    Spring 通知类型 通过前面的学习可以知道,通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口. Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型,如表 1 所示. 表 1 Spring 通知的 5 种类型 名称 说明 org.springframework.aop.MethodBeforeAdvice(前置通知) 在方法之前自动执行的通知称为前置通知,可以

  • 这一次搞懂Spring代理创建及AOP链式调用过程操作

    前言 AOP,也就是面向切面编程,它可以将公共的代码抽离出来,动态的织入到目标类.目标方法中,大大提高我们编程的效率,也使程序变得更加优雅.如事务.操作日志等都可以使用AOP实现.这种织入可以是在运行期动态生成代理对象实现,也可以在编译期.类加载时期静态织入到代码中.而Spring正是通过第一种方法实现,且在代理类的生成上也有两种方式:JDK Proxy和CGLIB,默认当类实现了接口时使用前者,否则使用后者:另外Spring AOP只能实现对方法的增强. 正文 基本概念 AOP的术语很多,虽然

  • Spring AOP里的静态代理和动态代理用法详解

    什么是代理? 为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间 什么是静态代理? 由程序创建或特定工具自动生成源代码,在程序运行前,代理类的.class文件就已经存在 通过将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的. 优点

  • Spring AOP中的JDK和CGLib动态代理哪个效率更高?

    一.背景 今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib动态代理哪个效率更高? 二.基本概念 首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理,另一种是CGLib的方式. 自Java 1.3以后,Java提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了Spring的很多地方. JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler.其中,Invoc

  • Spring AOP拦截-三种方式实现自动代理详解

    这里的自动代理,我讲的是自动代理bean对象,其实就是在xml中让我们不用配置代理工厂,也就是不用配置class为org.springframework.aop.framework.ProxyFactoryBean的bean. 总结了一下自己目前所学的知识. 发现有三种方式实现自动代理 用Spring一个自动代理类DefaultAdvisorAutoProxyCreator: <bean class="org.springframework.aop.framework.autoproxy.

  • Spring AOP注解失效的坑及JDK动态代理

    @Transactional @Async等注解不起作用 之前很多人在使用Spring中的@Transactional, @Async等注解时,都多少碰到过注解不起作用的情况. 为什么会出现这些情况呢?因为这些注解的功能实际上都是Spring AOP实现的,而其实现原理是通过代理实现的. JDK动态代理 以一个简单的例子理解一下JDK动态代理的基本原理: //目标类接口 public interface JDKProxyTestService { void run(); } //目标类 publ

  • spring基础概念AOP与动态代理理解

    一.代理模式 代理模式的英文叫做Proxy或Surrogate,中文都可译为"代理",所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 以简单模拟事务的执行过程说明各种代理区别 1.1 静态代理 由程序员创建或由特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. public interface PersonDao { vo

  • Spring AOP代理详细介绍

    Spring AOP代理详细介绍 前言: 一开始我对spring AOP还是属于一知半解的状态,这几天遇到一个问题,加上又查看了一些Spring相关知识,感觉对这个问题有了更深刻的认识.所以写下来分享一下. 我们知道,Spring支持多种AOP方式,Spring自己的基于代理的AOP和AspectJ的基于编织(weaving)的AOP.如果一个类实现了一个或多个接口,那么Spring就会使用默认的JDK动态代理,如果没有实现任何接口,就会使用cglib来代理.当然我们也可以手动改变这些设置.这也

  • Spring AOP手动实现简单动态代理的代码

    什么是AOP我们先来看一张图 图中A就是通知,比如你要给每个方法前都加一个before()方法,目标类的每一个方法叫joinpoint(切入点),每个切入点都会用到通知,把通知和切入点连起来,点成线,线成面,这就是切面,也就是AOP,下面我们来简单写个小例子来实现一下 目标类的接口 public interface UserService { public void addUser() ; public void updateUser(); public void deleteUser(); }

  • Spring温故而知新系列教程之AOP代理

    AOP的概念 AOP:Aspect-Oriented Programming(面向切面编程),维基百科的解释如下:Aspect是一种新的模块化机制,用来描述分散在对象.类或者函数中的横切关注点,从关注点中分离出横切关注点是面向切面的程序设计的核心概念.分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不在含有针对特定领域问题的代码的调用,业务逻辑同特定领域问题的关系通过切面来封装.维护,这样原本分散在整个应用程序中的变动就可以很好地管理起来.从AOP的角度,应用可以分为横切

  • 在AOP中Spring生成代理类的两种方式

    Java 动态代理.具体有如下四步骤: 通过实现 InvocationHandler 接口创建自己的调用处理器: 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类: 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型: 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入. 在AOP中,Spring通过生成代理类,来完成切面的织入. Spring生成代理类有2种方式. 如果目标对象实现的是一个接口,Sprin

  • 利用spring aop实现动态代理

    下面由我来给大家展示用spring aop实现动态代理的例子(电脑打印) 下面就看一下具体的代码: 先定义一个打印机的接口 package aop007_ComPrint; public interface Print { public void ColorPrint(); //彩色打印 public void WhitePrint(); //黑白打印 } 然后定义两个实现类,分别实现彩色打印和黑白打印 package aop007_ComPrint; public class ColorPri

随机推荐