Spring AOP (面向切面编程)原理与代理模式—实例演示

这篇具有很好参考价值的文章主要介绍了Spring AOP (面向切面编程)原理与代理模式—实例演示。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 一、AOP介绍和应用场景

Spring 中文文档 (springdoc.cn)

Spring | Home 官网

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

1、AOP介绍(为什么会出现AOP ?)       

        Java是一个面向对象(OOP)的语言,但它有一些弊端。虽然使用OOP可以通过组合或继承的方式来实现代码的重用。但当我们需要为多个不具有继承关系的对象(一般指的是两个不同的类,它们之间没有继承自同一个父类或接口。它们无法通过继承来共享属性和方法。)引入一个公共行为,例如日志、权限验证、事务等功能时,同样的代码仍然会分散到各个方法中。如果想关闭某个功能或是对其进行修改,就必须修改所有的相关方法,这样做不便于维护,而且有大量重复代码。于是AOP的出现弥补了OOP的这点不足。

         AOP 是 Spring 框架提供的一个重要特性,与OOP(面向对象编程)不同的是,AOP主张将程序中相同的业务逻辑进行横向的隔离,将重复的业务逻辑抽取到一个独立的模块中,通过在运行时动态地将代码逻辑织入到目标对象的方法中,实现与业务逻辑解耦和重复代码的消除,提高了代码的可维护性和可扩展性。

2、AOP应用场景

这是官方文档中对应用场景进行的描述:

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Authentication 权限 ;Caching 缓存 ; Context passing 内容传递;

Error handling 错误处理;Lazy loading 懒加载;Debugging 调试;

Logging 日志 ,tracing,profiling and monitoring 记录跟踪 优化 校准;

Performance optimization 性能优化 ;Persistence 持久化   ; Resource pooling 资源池

Synchronization 同步  ;Transactions 事务

二、AOP原理

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

 执行过程依次是

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

1、 在 Spring 中,创建 Bean 实例都是从 getBean()方法开始的, 在实例创建之后,Spring 容器将根据 AOP 的配置去匹配目标类的类名,看目标类的类名是否满足切面规则。如果满足满足切面规则,就会调用 ProxyFactory 创建代理 Bean 并缓存到 IoC 容器中。根据目标对象的自动选择不同的代理策略。如果目标类实现了接口,Spring 会默认选择 JDK Proxy,如果目标类没有实现接口,Spring 会默认选择 Cglib Proxy, 当然,我们也可以通过配置强制使用 Cglib Proxy

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

2、当用户调用目标对象的某个方法时,将会被一个叫做AopProxy 的对象拦截,Spring 将所有的调用策略封装到了这个对象中,它默认实现了 InvocationHandler 接口,也就是调用代理对象的外层拦截器。在这个接口的 invoke()方法中,会触发 MethodInvocation 的 proceed()方法。在这个方法中会按顺序执行符合所有 AOP 拦截规则的拦截器链。 

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

3、Spring AOP 拦截器链中的每个元素被命名为 MethodInterceptor,其实就是切面配置中的 Advice 通知。这个回调通知可以简单地理解为是新生成的代理 Bean 中的方法。也就是我们常说的被织入的代码片段,这些被织入的代码片段会在这个阶段执行。

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

4、MethodInterceptor 接口也有一个 invoke()方法,在 MethodInterceptor 的 invoke()方法中会触发对目标对象方法的调用,也就是反射调用目标对象的方法。

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

三、什么是代理模式

        当谈论代理模式时,可以通过两种常见例子进行解释:买火车票和Windows里的快捷方式。

  1. 买火车票:假设你需要买一张火车票,但你不想亲自去火车站排队购票。那么你可以找一个代理人,让他帮你完成购票的过程。代理人负责排队、选座位、支付等操作,并最终将车票交给你。在这个例子中,代理人扮演着你与火车站之间的中介角色,你通过代理人完成了购票过程,同时也避免了亲自去火车站排队的麻烦。

  2. Windows里的快捷方式:在Windows系统中,你可以使用桌面上的快捷方式来访问某个程序或文件。快捷方式实际上是一个代理,它引用了目标程序或文件的位置信息,并提供了一个方便的入口,使得你可以通过点击快捷方式来快速访问目标内容。快捷方式隐藏了底层的具体实现细节,简化了用户的操作过程。

下面是买火车票的示例代码:

// 创建一个抽象主题接口
interface Ticket {
    void purchase();
}

// 创建真实主题类,即需要购买火车票的对象
class TrainTicket implements Ticket {
    public void purchase() {
        System.out.println("购买火车票");
    }
}

// 创建代理类,即代理对象
class TicketProxy implements Ticket {
    private TrainTicket ticket;

    public void purchase() {
        if (ticket == null) {
            ticket = new TrainTicket();
        }
        // 通过代理对象调用真实对象的方法
        prePurchase();
        ticket.purchase();
        postPurchase();
    }

    private void prePurchase() {
        System.out.println("代理人处理排队、选座位等操作");
    }

    private void postPurchase() {
        System.out.println("代理人将车票交给顾客");
    }
}

// 客户端代码
public class ProxyPatternExample {
    public static void main(String[] args) {
        // 创建代理对象,并通过它来购买火车票
        Ticket ticketProxy = new TicketProxy();
        ticketProxy.purchase();
    }
}

        在上述示例中,Ticket是一个抽象主题接口定义了购票的方法。TrainTicket是真实主题类,代表了真正需要购票的对象。TicketProxy是代理类,实现了Ticket接口,它维护了一个对TrainTicket对象的引用,并在调用purchase方法前后进行了一些额外的操作,如排队和交票。通过创建代理对象并调用其purchase方法,我们可以间接地让代理对象完成购票过程,并在其中添加一些特定功能,而无需直接访问真实主题对象。

        而代理模式又分为两种,动态代理和静态代理。它们都是面向对象中的代理模式,作用都是为了在客户端与目标对象之间起到一个中介作用,拦截对目标对象的访问,以达到增强目标对象的功能或控制其访问权限的目的。

两者的区别如下:

        静态代理是在编译时期就已经确定的代理关系,代理类和目标类都是在编译时期就已经存在的。在静态代理中,需要手动创建代理类,代理类和目标类之间的关系是固定的。每当需要代理一个新的接口或者类时,都需要手动编写一个新的代理类。

        而动态代理是在运行时动态生成的代理类。通过 Java 提供的 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口,可以在运行时动态生成代理对象。动态代理不需要手动编写代理类,它在运行时根据接口信息动态地生成代理对象。使用动态代理时,代理类和目标类之间的关系是灵活的。只需要定义一个普通的接口,通过 InvocationHandler 来处理方法调用,可以代理多个不同的接口或类。

        动态代理的优点在于可以减少代码量,避免了编写大量重复的代理类。同时,它还可以实现更灵活的代理逻辑,可以在运行时动态地修改代理行为。

        总的来说,动态代理和静态代理都是代理模式的实现方式,静态代理通过手动编写代理类实现,而动态代理则通过反射机制在运行时动态生成代理类。

四、Spring AOP 的代理机制

        Spring 在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。Spring AOP 的底层是通过以下 2 种动态代理机制,为目标对象(Target Bean)执行横向织入的。

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

        在 Spring AOP 的配置中,可以通过 XML 或注解的方式声明切入点和切面。XML 方式需要配置切入点表达式、通知类型(如前置通知、后置通知等)和切面类的引用;注解方式则通过在切面类上添加注解来指定切入点和通知类型。

        Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

JDK代理和CGLIB代理有什么区别?

都不需要创建代理类,JDK 在运行时为我们动态的来创建,JDK代理是接口 。若目标类不存在接口,则使用Cglib生成代理,不管是JDK代理还是Cglib代理本质上都是对字节码进行操作。

代理技术 描述
JDK 动态代理 Spring AOP 默认的动态代理方式,若目标对象实现了若干接口,Spring 使用 JDK 的 java.lang.reflect.Proxy 类进行代理。
CGLIB 动态代理 若目标对象没有实现任何接口,Spring 则使用 CGLIB 库生成目标对象的子类,以实现对目标对象的代理。

注意:由于被标记为 final 的方法是无法进行覆盖的,因此这类方法不管是通过 JDK 动态代理机制还是 CGLIB 动态代理机制都是无法完成代理的。

面试:让你实现一个JDK实现动态代理?你的思路是什么?

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义接口
interface Hello {
    void sayHello();
}

// 实现 InvocationHandler 接口
class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法调用前后添加自定义逻辑
        System.out.println("Before method invocation");
        Object result = method.invoke(target, args);
        System.out.println("After method invocation");
        return result;
    }
}

public class DynamicProxyExample {
    public static void main(String[] args) {
        // 创建被代理对象
        Hello hello = new HelloImpl();

        // 创建 InvocationHandler 实例
        MyInvocationHandler handler = new MyInvocationHandler(hello);

        // 创建代理对象
        Hello proxyHello = (Hello) Proxy.newProxyInstance(
            hello.getClass().getClassLoader(),
            hello.getClass().getInterfaces(),
            handler
        );

        // 调用代理对象的方法
        proxyHello.sayHello();
    }
}

// 实现接口
class HelloImpl implements Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }
}

如果使用CGLIB来实现动态代理要怎么实现呢 ?

引入 CGLIB 的依赖。在 Maven 项目中,可以添加以下依赖项到 pom.xml 文件中:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
创建一个普通的类作为目标类,该类不需要实现任何接口。例如:
public class TargetClass {
    public void doSomething() {
        System.out.println("Doing something...");
    }
}
创建一个实现了 MethodInterceptor 接口的类,该类负责处理方法的调用。在 intercept 方法中,可以编写逻辑来处理代理对象的操作。例如:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method invocation");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method invocation");
        return result;
    }
}
使用 CGLIB 创建代理对象。通过调用 Enhancer.create 方法,传入目标类的 Class 对象和 MethodInterceptor 对象,创建代理对象。例如:
import net.sf.cglib.proxy.Enhancer;

public class CglibDynamicProxyExample {
    public static void main(String[] args) {
        // 创建目标类的实例
        TargetClass target = new TargetClass();

        // 创建 MethodInterceptor 实例
        MyMethodInterceptor interceptor = new MyMethodInterceptor();

        // 使用 Enhancer 创建代理对象
        TargetClass proxy = (TargetClass) Enhancer.create(
            target.getClass(),
            interceptor
        );

        // 调用代理对象的方法
        proxy.doSomething();
    }
}
运行上述代码,会输出以下结果:
Before method invocation
Doing something...
After method invocation

CGLIB 可以在运行时生成目标类的子类,并通过拦截器拦截目标方法的调用。这样可以实现对目标方法的增强或拦截操作

五、实例—实现AOP对日志的记录 

步骤:

先要引入aop依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

然以根据以下三个步骤 

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

 Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式


import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * 使用@Before在切入点开始处切入内容
 * 使用@After在切入点结尾处切入内容
 * 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
 * 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
 * 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
 */

@Aspect /**Description:  使之成为切面类*/
@Component /**Description: 把切面类加入到IOC容器中*/
public class AopLog {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //线程局部的变量,解决多线程中相同变量的访问冲突问题。
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    //定义切点 1
    @Pointcut("execution(public * com.example..*.*(..))")
    public void aopWebLog() {
    }
    //定义切点 2
    @Pointcut("execution(public * com.example..*.*(..))")
    public void myPointcut() {
    }

    @Before("aopWebLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 记录下请求内容
        logger.info("URL : " + request.getRequestURL().toString());
        logger.info("HTTP方法 : " + request.getMethod());
        logger.info("IP地址 : " + request.getRemoteAddr());
        logger.info("类的方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        //logger.info("参数 : " + Arrays.toString(joinPoint.getArgs()));
        logger.info("参数 : " + request.getQueryString());
    }

    @AfterReturning(pointcut = "aopWebLog()",returning = "retObject")
    public void doAfterReturning(Object retObject) throws Throwable {
        // 处理完请求,返回内容
        logger.info("应答值 : " + retObject);
        logger.info("费时: " + (System.currentTimeMillis() - startTime.get()));
    }

    //抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。
    @AfterThrowing(pointcut = "aopWebLog()", throwing = "ex")
    public void addAfterThrowingLogger(JoinPoint joinPoint, Exception ex) {
        logger.error("执行 " + " 异常", ex);
    }


    @Around("myPointcut()")
    public Object mylogger (ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getTarget().getClass().toString();
        String methodName = pjp.getSignature().getName();
        Object[] arry = pjp.getArgs();
        ObjectMapper mapper = new ObjectMapper();
        logger.info("调用前:"+className+":"+methodName+"传递的参数为:"+mapper.writeValueAsString(arry));
        Object obj = pjp.proceed();
        logger.info("调用后"+className+":"+methodName+"返回值为:"+mapper.writeValueAsString(obj));
        return obj;
    }

}
@RestController
public class AopLogController {
//    @GetMapping("/aoptest")
//    public String aVoid(){
//        return "hello aop test";
//    }

    @GetMapping("/hello")
    public String hello (@RequestParam("name")String name,@RequestParam("age")String age){
        return "hello"+name+"age"+age;
    }
}

运行:

Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式

 可以看到日志已经打印出来了。Spring AOP (面向切面编程)原理与代理模式—实例演示,Java后端,代理模式,spring,java,spring boot,后端,设计模式文章来源地址https://www.toymoban.com/news/detail-614823.html

到了这里,关于Spring AOP (面向切面编程)原理与代理模式—实例演示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 认识 spring AOP (面向切面编程) - springboot

    本篇介绍什么是spring AOP, AOP的优点,使用场景,spring AOP的组成,简单实现AOP 并 了解它的通知;如有错误,请在评论区指正,让我们一起交流,共同进步! 本文开始 AOP: 面向切面编程,也就是面向某一类编程,对某一类事情进行统一处理; spring AOP: 是实现了AOP这种思想的一

    2024年02月14日
    浏览(50)
  • Spring AOP(面向切面编程)和方法拦截

    Spring AOP(面向切面编程)和方法拦截 Spring是一款广泛使用的Java开发框架,提供了丰富的功能和工具,用于简化企业级应用程序的开发。其中一个重要的特性是面向切面编程(AOP)和方法拦截。本文将介绍Spring AOP和方法拦截的概念、工作原理以及在实际开发中的应用。 在软

    2024年02月05日
    浏览(45)
  • 切面的魔力:解密Spring AOP 面向切面编程

    目录 一、AOP简介 1.1 什么是AOP ? 1.2 什么是面向切面编程 ? 1.3 AOP 的特点 二、 AOP的基本概念解读 2.1 AOP的基本概念 2.2 AOP 概念趣事解读 三、代码情景演示 3.1 编写目标对象(超级英雄们正常的行动) 3.2 编写通知类 3.2.1 前置通知 3.2.2 后置通知 3.2.3 异常通知 3.2.4 环绕通知

    2024年02月11日
    浏览(57)
  • JAVA:使用 Spring AOP 实现面向切面编程

    1、简述 在现代的软件开发中,面向切面编程(AOP)是一种重要的编程范式,它允许我们将横切关注点(如日志记录、性能监控、事务管理等)从应用程序的核心业务逻辑中分离出来,以提高代码的模块化和可维护性。Spring 框架提供了强大的 AOP 支持,使得我们可以轻松地实

    2024年04月13日
    浏览(45)
  • 【Java 初级】Spring核心之面向切面编程(AOP)

    tip:作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。 💕💕 推荐: 体系化学习Java(Java面试专题) AOP(面向切面编程)是一种编程范式,用于将横切关注点(如日志记录、性能统计等

    2024年02月04日
    浏览(50)
  • spring6-AOP面向切面编程

    1、场景模拟 搭建子模块:spring6-aop 1.1、声明接口 声明计算器接口Calculator,包含加减乘除的抽象方法 1.2、创建实现类 1.3、创建带日志功能的实现类 1.4、提出问题 ①现有代码缺陷 针对带日志功能的实现类,我们发现有如下缺陷: 对核心业务功能有干扰,导致程序员在开发核

    2024年02月08日
    浏览(54)
  • 【Spring AOP】结合日志面向切面编程 两种写法

            这里需要提前了解什么是Spring的AOP(Aspect Oriented Programming)。是在OOP(面向对象)思想的一种拓展思想。 简单来说就是将某个代码块嵌入到其它的代码块中 。笔者先前学Spring也有学什么IoC啊AOP啊,但实际上没有用过、就那听过学过没啥用的。。没会儿就忘记了。

    2024年02月13日
    浏览(44)
  • Spring AOP使用指南: 强大的面向切面编程技术

    🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础

    2024年02月09日
    浏览(51)
  • 【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用

    【JavaEE】 AOP(1) 1.1 AOP 与 Spring AOP AOP ( A spect O riented P rogramming),是一种思想,即 面向切面编程 Spring AOP 则是一个框架,Spring项目中需要引入依赖而使用 AOP和Spring AOP的关系就相当于IoC和DI Spring AOP让开发者能够半自动的开发AOP思想下实现的功能 1.2 没有AOP的世界是怎样的

    2024年02月11日
    浏览(46)
  • Spring AOP:面向切面编程在实际项目中的应用

    🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础

    2024年02月09日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包