Spring AOP的实现方式与原理

目录

认识IOC与AOP

AOP的实现方式

@Aspect注解实现AOP

自定义注解实现AOP

Spring AOP原理

代理模式

静态代理和动态代理

JDK动态代理

CGLIB动态代理

Spring AOP实现的哪种代理


认识IOC与AOP

IOC又称为控制反转,也就是控制权发生了反转.在传统的程序中,我们是需要自己来手动创建对象的,但是在Spring实现IOC思想后,就可以直接交给Spring来管理创建.所以控制反转就是将对对象的创建销毁权利交给SPring来处理,我们就不需要关心. 举个例子: 原来,我们需要在A类中调用B类的方法,传统的方式就是我们直接在A中new出B类的对象,然后再调用B类的方法.这里虽然可以实现效果.但是会存在一个很大的问题:如果你的需求发生了变化,你就需要对源代码进行修改,这样不仅是导致代码工作量巨大,还容易发生错误.这种代码属于高耦合.但当实现IOC思想后,我们创建B对象就交给了Spring来管理.在Spring中,B对象被看成一个Bean对象. Bean对象都由spring来创建和管理..这样的话,我们需要获取对象,就从主动new变成了被动等Spring来创建. 从主动变成被动,这就可以理解为控制反转.这样就可以大大降低代码的耦合性. 所以IOC也就是依赖类不由程序猿实例化,而是通过Spring容器来帮助我们new好指定的实例,且将实例注入到需要的类中.

而AOP称为面向切面编程,这里的切面就是特指一些特定的问题.向我们的拦截器,统一结果返回,统一异常处理,这也是AOP思想的一种体现.简单来说,AOP就是对一类事情的集中处理.AOP可以说是对OOP的补充. 面向对象就是将食物的特性和行为抽象成一个对象,将它们的特征和行为封装成一个类,统一调用. 且面向切面就是将其中特定的问题给提取出来,等需要用的时候才切入.就比如有一个people类,它们都有身高,体重,年龄等属性和吃饭,睡觉等行为.但是生病去医院看病这个行为是只有一部分人才会发生的.AOP就是将看病这个行为给提取出来,然后等到需要这个功能的时候再给切入到需要的方法中.这样就可以减少系统的重复代码和模块之间降低耦合度.

AOP的实现方式

Spring AOP有四种实现方式,第一种是通过@Aspect注解来实现的,第二种则是通过自定义注解来实现的,第三种是通过SPring的API来实现的(也就是xml的方式). 第四种就是基于动态代理来实现的,可以说上面是三种都是基于动态代理在实现的.

@Aspect注解实现AOP

@Aspect表示一个切面类,而和它配合使用的还有多个注解:

通知类型:

@Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏   
@Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏
@After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏
@AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏
@AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏
@Slf4j
@Aspect
public class TimeAspect {
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) {
        long start = System.currentTimeMillis();
        Object result;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info(joinPoint.getSignature() + "执行时间: " + (System.currentTimeMillis()-start)+ "ms");
        return result;
    }
}

自定义注解实现AOP

这里是使用@annotatoin注解来实现的.我们还需要先实现一个自定义注解再将自定义注解放到我们需要增强的方法上.

自定义注解:

package com.example.demo.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

 切面类:

@Aspect
@Slf4j
public class AspectDemo1 {

    @Before("@annotation(com.example.demo.aspect.MyAspect)")
    public void before1() {
        log.info("before1方法执行");
    }

    @After("@annotation(com.example.demo.aspect.MyAspect)")
    public void after1() {
        log.info("after1方法执行");
    }
}

需要增强的方法:

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @MyAspect
    @RequestMapping("/t1")
    public void t1() {
        log.info("t1方法执行");
    }
    @MyAspect
    @RequestMapping("/t2")
    public void t2() {
        int a = 10 / 0;
        log.info("t2方法执行");

    }
}

Spring AOP原理

我们的Spring AOP是基于动态代理来实现的,这里我们分两部分来进行说明.

代理模式

代理模式就是为其他对象提供一种代理来控制对这个对象的访问. 它的作用就是通过提供一个类,让我们在调用目标方法的是,不再是直接调用,而是通过这个代理类来间接调用.因为再一些情况先,不适合直接引用另一个对象. 代理模式就好比我们的房屋中介一样: 房屋出租时,房东就将房屋授权给中介,由中介来代理看房,房屋咨询等.

静态代理和动态代理

我们可以根据代理的创建时期,代理分为静态代理和动态代理.

静态代理就是有程序猿来创建代理类,再对其进行编译,在程序运行前代理类就是class文件存在了.

动态代理则是在程序运行的时候,运用反射机制来创建.相比于静态代理来说,动态代理更加的灵活.我们不需要针对每一个目标对象单独创建一个代理对象,而是将这个工作推到程序运行再让JVM来执行.

Java中也对动态代理进行了实现,常见的动态代理有JDK动态代理和CGLIB动态代理.

JDK动态代理

它的实现步骤主要是:

1. 定义一个接口和它的实现类.

2.自定义一个类来实现InvocationHandler接口,且重写invoke方法,在invoke方法中我们会调用目标方法且自定义一些逻辑.

3. 通过proxy.newProxyInstance(ClassLoder loader, Class<?>[] interfaces, InvocationHandler h) 方法来创建.

实现InvocationHandler接口:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
 //⽬标对象即就是被代理对象
 private Object target;
 public JDKInvocationHandler(Object target) {
 this.target = target;
 }
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
 // 代理增强内容
 System.out.println("我是中介, 开始代理");
 //通过反射调⽤被代理类的⽅法
 Object retVal = method.invoke(target, args);
 //代理增强内容
 System.out.println("我是中介, 代理结束");
 return retVal;
 }
}

创建一个代理对象并使用:

public class DynamicMain {
 public static void main(String[] args) {
 HouseSubject target= new RealHouseSubject();
 //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
 HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
 target.getClass().getClassLoader(),
 new Class[]{HouseSubject.class},
 new JDKInvocationHandler(target)
 );
 proxy.rentHouse();
 }
}

通过这里,我们知道JDK动态代理在使用Proxy类的newProxyInstance方式创建代理类,它方法的第二个参数是interfaces,也就是被代理类实现的一些接口(这里就决定了JDK动态代理只能实现接口的类)

CGLIB动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类. 有些场景下, 我们的业务代码是直接实现的, 并没有接⼝定义. 为了解决这个问题, 我们可以⽤ CGLIB 动态代理机制来解决    

CGLIB实现步骤:

1. 定义一个被代理类

 2. 自定义MethodInterceptor,且重写intercept方法,intercept方式用来增强目标方法,和JDK的invoke方法类似.

2. 通过Enhancer类的create()来创建代理类.

自定义一个类实现MethodInerceptor接口:

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {
 //⽬标对象, 即被代理对象
 private Object target;
 public CGLIBInterceptor(Object target){
 this.target = target;
 }
 @Override
 public Object intercept(Object o, Method method, Object[] objects, 
MethodProxy methodProxy) throws Throwable {
 // 代理增强内容
 System.out.println("我是中介, 开始代理");
 //通过反射调⽤被代理类的⽅法
 Object retVal = methodProxy.invoke(target, objects);
 //代理增强内容
 System.out.println("我是中介, 代理结束");
 return retVal;
 }
}

创建代理类,并使用:

public class DynamicMain {
 public static void main(String[] args) {
 HouseSubject target= new RealHouseSubject();
 HouseSubject proxy= (HouseSubject) 
Enhancer.create(target.getClass(),new CGLIBInterceptor(target));
 proxy.rentHouse();
 }
}

这里Enhancer.create()生成对象的方法参数:

type: 被代理类的类型

callback: 自定义的被代理类MethodInterceptor

因为这里被代理类的类型参数为任意参数,所以CGLIB可以代理任意类.

Spring AOP实现的哪种代理

Spring Framework 和Spring Boot它们底层实现的都是JDK和CGLib动态代理. 但是它们不同之处在于:

Spring Framework如果代理的目标类是实现了接口的类,那就是使用JDK,如果代理的是没有实现接口的类,则会使用CGlib(而是,这里的实现的接口里面至少有一个自定义的方法,不然还是会CGLIB代理,因为这里就算我们将proxyTargetClass配置为了false,经过一些判断还是会为true)

Spring boot2.0 之后的版本默认配置的使用CGlib代理.代理的类无论是实现了接口还是没有实现都用CGlib代理,如果需要使用JDK代理,则需要去配置. 而Spring boot2.0之前的版本和Spring Framework一样.

它们之所以不同,就是因为代理工厂里面proxyTargetClass属性的默认配置不同. 如果为false 则实现了接口的类使用JDK,没有则使用CGLIB.如果为true,不管实现没实现都使用CGLIB.  也就是因为SpringFramework和Springboot2.0之前,ProxyTargetClass默认是false,而Springboot2.0之后默认是true.如果需要使用其他的代理,我们修改proxyTargetClass配置即可.


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/554245.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

结构体内存对齐

结构体内存对齐的规则 第一个成员在结构体对象的首地址处。其他成员变量要对齐到对齐数的整数倍。结构体对象的总大小是最大对齐数的整数倍。如果结构体内嵌套了结构体&#xff0c;嵌套的结构体对齐到自己的最大对齐数的整数倍处。结构体整个大小就是最大对齐数的整数倍。 对…

JS高级 - Promise使用方法详解

目录 一、什么是Promise 1.1 Promise的三种状态 二、Promise 基本用法 2.1 Promise基本使用 2.2 Promise使用时传参 2.3 Promise 链式调用 2.4 链式调用注意事项 三、Promise内置方法 3.1 Promise.all() 3.2 Promise.race() 3.3 Promise.allSettled() 3.4 Promise.…

1688商家自曝流量暴涨技巧!7天起店,仅需4步神操作!

经常有人问我1688&#xff0c;7天怎么起店&#xff1f;根据之前的一些经验分享一下&#xff0c;大概7天就能做到4位数以下的展现量&#xff0c;4步轻松完成。 新运营课堂第一步&#xff0c;进入卖家工作台&#xff0c;点击商品&#xff0c;查看单品被收藏次数及被加购次数&…

C++--浅拷贝和深拷贝

浅拷贝和深拷贝 1.浅拷贝 浅拷贝,多个指针指向同一段内存,出现一处指针修改数据,其它指针的数据也发生改变。 1.1 面向过程的浅拷贝(C方式) 如下代码: //下面程序,从键盘获取4个字符串,然后输出到屏幕 int main() {char buf[100];char* strArr[4];//长度为4的字符指针数组…

Unity面向切面编程

一直说面向AOP&#xff08;切面&#xff09;编程&#xff0c;好久直接专门扒出理论、代码学习过。最近因为某些原因&#x1f62d;还得再学学造火箭的技术。 废话不多说&#xff0c;啥是AOP呢&#xff1f;这里我就不班门弄斧了&#xff0c;网上资料一大堆&#xff0c;解释的肯定…

广东海洋大学成功部署(泰迪智能科技)大数据人工智能实验室建设

广东海洋大学简称广东海大&#xff0c;坐落于广东省湛江市&#xff0c;是国家海洋局与广东省人民政府共建的省属重点建设大学、广东省高水平大学重点学科建设高校、粤港澳高校联盟成员 &#xff0c;入选卓越农林人才教育培养计划&#xff0c;是教育部本科教学水平评估优秀院校。…

【SQL】数据库SQL语句

1、主键 主键值唯一&#xff0c;不可修改&#xff0c;不能为空&#xff0c;删除不能重用 2、数据类型&#xff08;常用&#xff09; char int float date timestamp 3、select select * from data; select xx,xxx from data;//取部分行 select * from data limit 100; //限…

msyql中的四大日志

日志 错误日志二进制日志作用日志格式日志查看日志删除 查询日志慢查询日志 错误日志 错误日志是MySQL中最重要的日志之一&#xff0c;它记录了当MySQL启动和停止时&#xff0c;以及服务器子啊运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时…

angular node版本问题导致运行出错时应该怎么处理

如下图所示&#xff1a; 处理方式如下&#xff1a; package.json中start跟build中添加&#xff1a;SET NODE_OPTIONS--openssl-legacy-provider即可

SSRF+Redis未授权getshell

SSRFRedis未授权getshell 1.前言 当一个网站具有ssrf漏洞&#xff0c;如果没有一些过滤措施&#xff0c;比如没过滤file协议&#xff0c;gophere协议&#xff0c;dict等协议&#xff0c;就会导致无法访问的内网服务器信息泄露&#xff0c;甚至可以让攻击者拿下内网服务器权限 …

pixhawk控制板的ArduPilot固件编译

0. 环境 - ubuntu18&#xff08;依赖python2和pip&#xff0c;建议直接ubuntu18不用最新的&#xff09; - pixhawk 2.4.8 - pixhawk 4 1. 获取源码 # 安装git sudo apt install git # 获取源码 cd ~/work git clone --recurse-submodules https://github.com/ArduPilot/a…

腾讯AI Lab:“自我对抗”提升大模型的推理能力

本文介绍了一种名为“对抗性禁忌”&#xff08;Adversarial Taboo&#xff09;的双人对抗语言游戏&#xff0c;用于通过自我对弈提升大型语言模型的推理能力。 &#x1f449; 具体的流程 1️⃣ 游戏设计&#xff1a;在这个游戏中&#xff0c;有两个角色&#xff1a;攻击者和防守…

VsCode调试远程服务器上面的Docker容器

第一步 VsCode 连接ssh 下载安装VsCode(Visual Studio Code)&#xff0c;首次安装会提示你安装Chinese(Simplified)中文简体&#xff0c;安装完后重新打开就是汉化界面了。在左边侧边栏找到扩展选项&#xff0c;然后安装Remote Development插件&#xff0c;里面包含了Remote S…

糖尿病可能是一团虚火,肝肾同源,肝阴不足。

其实对于很多的糖尿病患者来说&#xff0c;他的问题本质可能是一团虚火&#xff0c;就拿前段时间我的门诊一个患者为例&#xff0c;之前患有高血压&#xff0c;总是眩晕烦躁&#xff0c;常常失眠&#xff0c;大概近四个月出现多饮、多尿怎么喝水也不解渴&#xff0c;经过检查确…

每日一题---OJ题: 链表的回文结构

片头 嗨! 小伙伴们,大家好! 今天我们来一起学习这道OJ题--- 链表的回文结构 嗯...这道题好像不是很难,我们来分析分析 举个例子: 我们可以看到,上图中的两个链表都是回文结构: 即链表的回文结构是指一个链表中的结点值从前往后读和从后往前读都是一样的结构。也就是说&#xf…

春秋云境:CVE-2022-32991[漏洞复现]

从CVE官网查询该漏洞相关信息 该漏洞是由于welcome.php中的eid参数包含了SQL注入漏洞 则我们的目标就在于寻找welcome.php地址以及相关的可注入eid参数 开启靶机 先在页面正常注册、登录一个账号。密码随便填 进入了home目录&#xff0c;这里有三个话题可以选择开启 随便选…

word批量修改表格样式

利用宏&#xff0c;批量选中表格&#xff0c;然后利用段落和表设计来操作。 利用宏&#xff0c;批量选中表格&#xff0c;参考百度安全验证段落&#xff0c;表格里面的内容有空格&#xff0c;应该是有缩进&#xff0c;在段落中去掉缩进&#xff0c;即缩进-特殊&#xff0c;选择…

Next.js 14 App Router引入 farmer-motion 初始化异常解决,顺带学点知识

前言 farmer-motion 是一个非常好用的动画库&#xff0c;当然用来做组件切换和路由切换过渡更不在话下。 记录一下&#xff0c;Next.js 14 App Router 下引入初始化异常的解决姿势&#xff0c;顺带扯一下 next.js 的知识点&#xff1b; 问题 过渡组件代码 我们拿 farmer-m…

https证书是什么,怎么申请

https证书的名称有很多&#xff0c;其本名是SSL/TLS数字证书&#xff0c;本意是实现https访问的证书&#xff0c;故而很多人会称之为https证书&#xff0c;又因为其需要部署于域名服务器之上&#xff0c;所以也有人称之为域名证书。 所以https证书又名SSL证书、域名证书等。 h…

SPN的相关利用(上)

什么是SPN 服务主体名称(SPN)是服务实例&#xff0c;可以理解为一个服务&#xff0c;比如mssql,http等等的唯一标识符。如果在整个林或域中的计算机上安装多个服务实例&#xff0c;则每个实例都必须具有自己的 SPN&#xff0c;Kerberos 身份验证使用 SPN 将服务实例与服务登录…