前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2023新版Spring6全新讲解-核心内容之AOP

2023新版Spring6全新讲解-核心内容之AOP

作者头像
用户4919348
发布2023-05-28 09:34:30
3000
发布2023-05-28 09:34:30
举报
文章被收录于专栏:波波烤鸭

Spring核心之AOP

一、前置基础-代理模式

  在学习Spring的AOP之前我们需要补充下设计模式中的代理模式。这块是理解AOP的必备基础内容。

image.png
image.png

1. 静态代理

  若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和目标类会实现同一接口或是派生自相同的父类。

先定义公共接口

代码语言:javascript
复制
/**
 * 代理模式
 *    定义的公共接口
 */
public interface SomeService {
    String doSome();
}

然后定义目标对象:

代码语言:javascript
复制
/**
 * 代理模式
 *    目标对象:Target Object
 */
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome() {
        System.out.println("目标对象执行了。。。");
        return "Hello";
    }
}

然后定义我们的代理对象:

代码语言:javascript
复制
/**
 * 代理模式
 *    代理类:需要和目标对象实现相同的接口
 */
public class SomeProxy implements SomeService{

    // 代理对象持有的目标对象
    private SomeService target;

    public SomeProxy(SomeService target){
        this.target = target;
    }

    /**
     * 代理对象需要增强的方法
     * @return
     */
    @Override
    public String doSome() {
        System.out.println("目标对象执行之前");
        // 应该需要让目标对象来完成核心的业务
        String msg = target.doSome();
        System.out.println("目标对象执行之后");
        return msg.toUpperCase();
    }
}

最后做测试:

代码语言:javascript
复制
    /**
     * 静态代理的测试
     */
    @Test
    public void test1(){
        // 获取目标对象
        SomeService target = new SomeServiceImpl();
        // 获取代理对象
        SomeService proxy = new SomeProxy(target);
        // 通过代理对象执行方法
        System.out.println(proxy.doSome());
    }

测试结果:

代码语言:javascript
复制
目标对象执行之前
目标对象执行了。。。
目标对象执行之后
HELLO

2. 动态代理

  代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。 代理类型 使用场景:

  • JDK动态代理:如果目标对象实现了接口,采用JDK的动态代理
  • CGLIB动态代理:如果目标对象没有实现了接口,必须采用CGLIB动态代理

2.1 JDK动态代理

  如何目标对象实现了相关的接口。那么我们就可以通过JDK动态代理来完成代理类的动态生成。

代码语言:javascript
复制
// 调用目标对象的方法
                        //String msg = target.doSome();
                        Object res = method.invoke(target, args);    /**
     * 实现JDK动态代理:目标对象必须实现相关的接口
     *    我们就不需要显示的定义代理类
     */
    @Test
    public void test2(){
        // 1.获取目标对象
        SomeService target = new SomeServiceImpl();
        // 2.获取代理对象
        SomeService proxy = (SomeService) Proxy.newProxyInstance(
                Test02.class.getClassLoader(), // 获取类加载器
                target.getClass().getInterfaces(), // 获取目标对象实现的所有的接口
                new InvocationHandler() { // 提供一个 InvocationHandler的对象
                    /**
                     * 该方法是代理对象执行目标对象方法的回调方法
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(method);
                        System.out.println(args);
                        System.out.println("----start------");
                        // 调用目标对象的方法
                        String msg = target.doSome();
                        System.out.println("----end------");
                        return msg.toUpperCase();
                    }
                }
        );
        // 3.通过代理对象来执行
        System.out.println("proxy.doSome() = " + proxy.doSome());
    }
}

执行结果:

image.png
image.png

2.2 CGLIB代理

  如果目标对象没有实现任何的接口。那么我们只能通过CGLIB代理的方式来实现了。同时我们需要单独的添加CGLIB的依赖。

代码语言:javascript
复制
<!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.3.0</version>
</dependency>

定义目标对象:注意不实现任何接口

代码语言:javascript
复制
/**
 * 目标对象的定义
 *    改目标对象没有实现任何的接口
 */
public class SomeService {

    public String doSome(){
        System.out.println("目标对象执行了....");
        return "Hello Cglib";
    }
}

然后定义cglib的代理对象

代码语言:javascript
复制
/**
 * Cglib的代理类
 */
public class CglibProxy implements MethodInterceptor {

    // 目标对象
    private SomeService target;

    public CglibProxy(SomeService target){
        this.target = target;
    }

    /**
     * 对外提供代理对象的方法
     * @return
     */
    public SomeService createTarget(){
        // 创建cglib的增强器
        Enhancer enhancer = new Enhancer();
        // 需要指定父类
        enhancer.setSuperclass(SomeService.class);
        // 代理后的回调对象
        enhancer.setCallback(this);
        return (SomeService) enhancer.create();
    }

    /**
     * 这个就是对应的增强的方法
     * @param o
     * @param method
     * @param objects
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("------start-----");
        //String msg = target.doSome();
        Object res = method.invoke(target, objects);
        System.out.println("------end-----");
        return msg.toUpperCase();
    }
}

然后测试:

image.png
image.png

二、AOP-面向切面编程

1. AOP 概述及相关概念

1.1 AOP概述

  AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程OOP的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 日志、事务、安全检查等

1.2 AOP 术语

  在学习AOP中我们会涉及到如下的相关概念

术语

说明

切面

切面泛指交叉业务逻辑。比如事务处理、日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强

织入

织入是指将切面代码插入到目标对象的过程。

连接点

连接点指切面可以织入的位置。

切入点

切入点指切面具体织入的位置。

通知(Advice)

通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

顾问(Advisor)

顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。 不仅指定了切入时间点,还可以指定具体的切入点

下面这个图会更加的形象些:

image.png
image.png

通知的类型:

通知类型

说明

前置通知(MethodBeforeAdvice)

目标方法执行之前调用

后置通知(AfterReturningAdvice)

目标方法执行完成之后调用

环绕通知(MethodInterceptor)

目标方法执行前后都会调用方法,且能增强结果

异常处理通知(ThrowsAdvice)

目标方法出现异常调用

最终通知(final Advice)

无论程序执行是否正常,该通知都会执行。类似于try…catch中finally代码块

image.png
image.png

2. 基于注解实现

2.1 基本介绍

  对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。在Spring中使用AOP开发时,一般使用AspectJ的实现方式.

image.png
image.png
image.png
image.png

相关说明:

  • 动态代理分为JDK动态代理和cglib动态代理
  • 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

2.2 基本案例

首先定义对应的接口

代码语言:javascript
复制
public interface Calculator {

    int add(int i, int j);

    int sub(int i ,int j);

    int mul(int i , int j);

    int div(int i , int j);
}

然后创建该接口的实现

代码语言:javascript
复制
package com.boge.service.impl;

import com.boge.service.Calculator;
import org.springframework.stereotype.Component;

@Component
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        return i / j;
    }
}

创建对应的切面类

代码语言:javascript
复制
/**
 * 切面类
 */
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect {

    /**
     * 前置通知:@Before()
     */
    @Before("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }
}

然后做对应的测试

代码语言:javascript
复制
    @Test
    public void test1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator bean = ac.getBean(Calculator.class);
        System.out.println(bean.add(4, 7));
        System.out.println(bean.sub(4, 7));
        System.out.println(bean.mul(4, 7));
    }

结果打印:

image.png
image.png

2.3 其他通知

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知
  • 最终通知

相关的通知的案例:

代码语言:javascript
复制
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect {

    /**
     * 前置通知:@Before()
     */
    @Before("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }

    /**
     * 后置通知:可以获取目标方法的返回结果
     */
    @AfterReturning(value = "execution(* com.boge.service.impl.*.*(..))",returning = "res")
    public void afterReturningMethod(JoinPoint joinPoint,Object res){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:" + methodName + "  返回结果:" + res);
    }

    /**
     * 环绕通知
     */
    @Around("execution(* com.boge.service.impl.*.*(..))")
    public Object  aroundMethod(ProceedingJoinPoint joinPoint){
        Object obj = null;
        try {
            System.out.println("环绕通知执行之前....");
            obj =joinPoint.proceed(); // 执行目标对象的方法
            System.out.println("环绕通知执行之后....");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知执行异常....");
        }finally {
            System.out.println("环绕通知执行....最终完成");
        }
        return obj;
    }

    /**
     * 异常通知
     */
    @AfterThrowing(value = "execution(* com.boge.service.impl.*.*(..))",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知:" + methodName + " " + ex);
    }

    /**
     * 最终通知
     */
    @After(value = "execution(* com.boge.service.impl.*.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("最终通知执行了..." + methodName);
    }
}

2.4 切入点表达式

  切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号

image.png
image.png

语法要求:

image.png
image.png

作用:

image.png
image.png

细节介绍:

  • 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
  • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
    • 例如:*.Hello匹配com.Hello,不匹配com.boge.Hello
  • 在包名的部分,使用“*…”表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分
    • *例如:Service匹配所有名称以Service结尾的类或接口
  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分
    • *例如:Operation匹配所有方法名以Operation结尾的方法
  • 在方法参数列表部分,使用(…)表示参数列表任意
  • 在方法参数列表部分,使用(int,…)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
    • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
    • 例如:execution(public int *…Service.(…, int)) 正确 例如:execution( int …Service.(…, int)) 错误

如果一个切入点表达式需要被重复的复用。那么我们可以通过@Pointcut注解来定义表达式。然后我们在通知调用即可:

代码语言:javascript
复制
package com.boge.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 切面类
 */
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect2 {

    /**
     * 定义一个切入点表达式
     */
    @Pointcut("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
    public void ponitCut(){

    }

    /**
     * 前置通知:@Before()
     */
    @Before("ponitCut()")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }

    /**
     * 后置通知:可以获取目标方法的返回结果
     */
    @AfterReturning(value = "ponitCut()",returning = "res")
    public void afterReturningMethod(JoinPoint joinPoint,Object res){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:" + methodName + "  返回结果:" + res);
    }

    /**
     * 环绕通知
     */
    @Around("ponitCut()")
    public Object  aroundMethod(ProceedingJoinPoint joinPoint){
        Object obj = null;
        try {
            System.out.println("环绕通知执行之前....");
            obj =joinPoint.proceed(); // 执行目标对象的方法
            System.out.println("环绕通知执行之后....");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知执行异常....");
        }finally {
            System.out.println("环绕通知执行....最终完成");
        }
        return obj;
    }

    /**
     * 异常通知
     */
    @AfterThrowing(value = "ponitCut()",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知:" + methodName + " " + ex);
    }

    /**
     * 最终通知
     */
    @After(value = "ponitCut()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("最终通知执行了22..." + methodName);
    }
}

3. 基于XML实现

  在Spring中AOP还有基于XML的实现方式。当然这种不是我们常用的方案。但是我们还是需要了解下

先定义对应的切面类:

代码语言:javascript
复制
/**
 * 切面类
 */
@Component
public class LogAspect3 {

    /**
     * 前置通知:@Before()
     */
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }

    /**
     * 后置通知:可以获取目标方法的返回结果
     */
    public void afterReturningMethod(JoinPoint joinPoint,Object res){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:" + methodName + "  返回结果:" + res);
    }

    /**
     * 环绕通知
     */
    public Object  aroundMethod(ProceedingJoinPoint joinPoint){
        Object obj = null;
        try {
            System.out.println("环绕通知执行之前....");
            obj =joinPoint.proceed(); // 执行目标对象的方法
            System.out.println("环绕通知执行之后....");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知执行异常....");
        }finally {
            System.out.println("环绕通知执行....最终完成");
        }
        return obj;
    }

    /**
     * 异常通知
     */
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知:" + methodName + " " + ex);
    }

    /**
     * 最终通知
     */
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("最终通知执行了..." + methodName);
    }
}

然后定义对应的配置文件

代码语言:javascript
复制
<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 添加扫描路径 -->
    <context:component-scan base-package="com.boge.*"></context:component-scan>
    <!-- 基于XML的AOP实现 -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="logAspect3">
            <!-- 定义切入点表达式 -->
            <aop:pointcut id="pointCut" expression="execution(* com.boge.service.impl.*.*(..))"/>
            <!-- 配置相关的通知 -->
            <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="res"></aop:after-returning>
            <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing>
            <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

然后测试即可

image.png
image.png
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring核心之AOP
  • 一、前置基础-代理模式
    • 1. 静态代理
      • 2. 动态代理
        • 2.1 JDK动态代理
        • 2.2 CGLIB代理
    • 二、AOP-面向切面编程
      • 1. AOP 概述及相关概念
        • 1.1 AOP概述
        • 1.2 AOP 术语
      • 2. 基于注解实现
        • 2.1 基本介绍
        • 2.2 基本案例
        • 2.3 其他通知
        • 2.4 切入点表达式
      • 3. 基于XML实现
      相关产品与服务
      云顾问
      云顾问(Tencent Cloud Smart Advisor)是一款提供可视化云架构IDE和多个ITOM领域垂直应用的云上治理平台,以“一个平台,多个应用”为产品理念,依托腾讯云海量运维专家经验,助您打造卓越架构,实现便捷、灵活的一站式云上治理。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档