Spring AOP实现原理-动态代理

news2024/11/16 15:29:39

目录

代理的基础概念

示例1:静态代理(场景:客户通过中介租房东的房子)

示例2:JDK动态代理实现房东、中介出租房屋

示例3:CGLib动态代理实现房东出租房屋

示例4:观察Spring IOC容器中代理对象详情


代理的基础概念

        AOP是基于代理模式实现切点方法的动态扩展。当切点目标类实现了接口,AOP通过JDK自带的动态代理扩展被代理对象方法的功能;当切点目标类未实现接口,Spring 通过CGLib组件实现扩展被代理对象方法功能。

        代理模式的核心是创建一个代理对象,代理对象内部包含封装了被代理对象,最终通过执行被代理对象的方法达到动态扩展方法的功能,代理模式分为静态代理和动态代理。

示例1:静态代理(场景:客户通过中介租房东的房子)

示意图如下:

EstateAgent(中介)和Landord(房东)都实现租房接口,Customer(客户)通过中介实现租房子,代码由以下各类组成:

1、接口(房屋出租接口)

package com.text.pattern;
//房屋出租接口
public interface RentalHouse {
    void rental();
}

2、房东类-实现租房接口

package com.text.pattern;
//房东类
public class Landlord implements RentalHouse{
    @Override
    public void rental() {
        System.out.println("xxx栋xxx房屋出租");
    }
}

3、中介类--实现租房接口

package com.text.pattern;
//房产中介
public class EstateAgent implements RentalHouse{
    private Landlord landlord;//被代理对象
    public EstateAgent(Landlord landlord) {
        this.landlord = landlord;
    }
    @Override
    public void rental() {
        System.out.println("中介收取客户中介费");
        this.landlord.rental();
    }
}

4、客户类-测试

package com.text.pattern;
//测试类
public class Customer {
    public static void main(String[] args) {
        System.out.println("客户找中介租房子");
        new EstateAgent(new Landlord()).rental();
    }
}

5、运行结果:

      

从运行结果中可以看出,房屋出租方法被扩展了中介收取客户手续费的功能。

        静态代理的劣势:如果需要对很多目标类方法进行扩展,就需要额外编写很多的代理类,通过动态代理可以实现一个代理类对一批目标方法进行扩展,也就实现了AOP

示例2:JDK动态代理实现房东、中介出租房屋

1、代理执行hander(ProxyInvocationHandler)

package com.text.pattern;

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

public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;//被代理对象
    public ProxyInvocationHandler(Object target) {
        this.target = target;
    }
    /**
     * @param proxy 代理对象
     * @param method 被代理对象的方法
     * @param args 被代理对象方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("目标类方法执行之前功能扩展...");
        Object ret = method.invoke(target, args);
        System.out.println("目标类方法执行之后功能扩展...");
        return ret;
    }
}

2、被代理对象实现的接口

package com.text.pattern;
//房屋出租接口
public interface RentalHouse {
    void rental();
}

 3、被代理对象,房东和中介都可以被代理

package com.text.pattern;
//房东类
public class Landlord implements RentalHouse{
    @Override
    public void rental() {
        System.out.println("房东出租xxx栋xxx房屋");
    }
}
package com.text.pattern;
//房产中介
public class EstateAgent implements RentalHouse{
    @Override
    public void rental() {
        System.out.println("中介出租xxx栋xxx房屋,并收取中介费");
    }
}

4、测试类 生成代理对象,房东的代理,中介的代理

package com.text.pattern;

import java.lang.reflect.Proxy;

//测试类
public class Customer {
    public static void main(String[] args) {
        RentalHouse landlord = new Landlord();//被代理对象
        RentalHouse proxyObj = (RentalHouse)Proxy.newProxyInstance(landlord.getClass().getClassLoader(),
                landlord.getClass().getInterfaces(), new ProxyInvocationHandler(landlord));
        proxyObj.rental();
        landlord = new EstateAgent();
        proxyObj = (RentalHouse)Proxy.newProxyInstance(landlord.getClass().getClassLoader(),
                landlord.getClass().getInterfaces(), new ProxyInvocationHandler(landlord));
        proxyObj.rental();
    }
}

5、运行结果:

 从运行结果可以看出,房东和中介都实现了租房的接口,并且都被代理,他们分别在租房的同时都实现了各自方法的扩展,即一个代理类(ProxyInvocationHandler)实现了对多个目标方法的动态扩展。

示例3:CGLib动态代理实现房东出租房屋

如果房东类没有实现接口,Spring 采用CGlib组件实现AOP功能

1、目标类(房东)

package com.text.pattern;
//房东类
public class Landlord2{
    public void rental() {
        System.out.println("房东2出租xxx栋xxx房屋");
    }
}

2、 代理工厂类(CglibProxyFactory)

package com.text.pattern;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyFactory implements MethodInterceptor {
    private Object target;//被代理的对象

    public CglibProxyFactory(Object target) {
        super();
        this.target = target;
    }

    //创建代理对象
    public Object getProxyInstance() {
        Enhancer en = new Enhancer();
        //父类
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        //创建子类代理对象
        return en.create();
    }


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
            throws Throwable {
        System.out.println("目标类方法执行之前功能扩展...");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("目标类方法执行之后功能扩展...");
        return result;
    }
}

3、测试类:

package com.text.pattern;

//测试类
public class Customer {
    public static void main(String[] args) {
        Landlord2 landlord2 = new Landlord2();//被代理对象
        CglibProxyFactory proxyFactory = new CglibProxyFactory(landlord2);
        //生成代理对象
        Landlord2 proxyObj = (Landlord2)proxyFactory.getProxyInstance();
        proxyObj.rental();
    }
}

4、运行结果:

从运行结果看出,代理对象对房东2出租方法实现了功能扩展。

示例4:观察Spring IOC容器中代理对象详情

代码如下:

1、学生DAO接口、DAO实现类、Service接口、Service实现类、Controller类

package com.text.dao;

public interface StudentDao {

    void getById(String id) throws Exception;
}
package com.text.dao.impl;

import com.text.dao.StudentDao;
import org.springframework.stereotype.Repository;

@Repository
public class StudentDaoImpl implements StudentDao {
    @Override
    public void getById(String id) throws Exception {
        Thread.sleep(1000);
        System.out.println("查询学生id=" + id + "的信息");
    }
}
package com.text.service;

import com.text.entity.Student;

public interface StudentService {
    public void save(Student student);
    public void deleteById(String id);
    public void updateById(String id) throws Exception;
    public Student searchById(String id) throws Exception;
}
package com.text.service.impl;

import com.text.dao.StudentDao;
import com.text.entity.Course;
import com.text.entity.Student;
import com.text.service.StudentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentDao studentDao;


    public StudentDao getStudentDao() {
        return studentDao;
    }

    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public void save(Student student) {
        System.out.println(student + "正在被保存...");
    }

    @Override
    public void deleteById(String id) {
        System.out.println("学生id=" + id + "的记录已被删除...");
    }

    @Override
    public void updateById(String id) throws Exception{
        System.out.println("学生id=" + id + "的记录正在被修改...");
        throw new Exception("修改学生信息出异常");
    }

    @Override
    public Student searchById(String id) throws Exception {
        System.out.println("已查询到学生id=" + id + "的记录...");
        Student student = new Student("张三",20,new Course("计算机"));
        return student;
    }
}
package com.text.controller;

import com.text.entity.Student;
import com.text.service.StudentService;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;

@Controller
public class StudentController {

    @Resource
    private StudentService studentService;

    public StudentService getStudentService() {
        return studentService;
    }

    public void setStudentService(StudentService studentService) {
        this.studentService = studentService;
    }

    public Student searchById(String id) throws Exception {
        return this.studentService.searchById(id);
    }

}

2、切面类 ,实现对com.text包及子包以“DaoImpl”结尾的类的所有方法和com.text包及子包以“Controller”结尾的类的所有方法的环绕通知

package com.text.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import java.util.Date;

/**
 * 定义方法切面类
 */
@EnableAspectJAutoProxy //开启AspectJ的注解方式
@Component
@Aspect //标识为切面类
public class MethodAspect {
    //配置环绕通知
    @Around("execution(public * com.text..*DaoImpl.*(..)) || execution(public * com.text..*Controller.*(..)) ")
    public void countMethodInvokeTime(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("目标方法执行之前记录初始时间...");
        Date startTime = new Date();
        try {
            proceedingJoinPoint.proceed();//执行目标方法 即:StudentDaoImpl.getById方法
            System.out.println("目标方法执行之后记录结束时间...");
            String methodName = proceedingJoinPoint.getTarget().getClass().getName() + "." +
                    proceedingJoinPoint.getSignature().getName();
            Date endTime = new Date();
            System.out.println(methodName + "方法执行总时长为:" + (endTime.getTime() - startTime.getTime()) + "毫秒");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

3、测试类

package com.text;

import com.text.controller.StudentController;
import com.text.dao.StudentDao;
import com.text.service.impl.StudentServiceImpl;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        //jdk动态代理对象
        StudentDao studentDao = context.getBean("studentDaoImpl", StudentDao.class);//代理对象
        Object target = ((Advised)studentDao).getTargetSource().getTarget();//方式1:获取被代理的原始对象
        Object singletonTarget = AopProxyUtils.getSingletonTarget(studentDao);//方式2:获取被代理的原始对象
        StudentServiceImpl studentService = context.getBean("studentServiceImpl", StudentServiceImpl.class);
        System.out.println("2种方式获取的被代理对象是否一致:" + (target == singletonTarget));
        //CGLib代理对象
        StudentController studentController = context.getBean("studentController", StudentController.class);//controller代理对象
        studentController.searchById("1");
        Object controllerTarget = AopProxyUtils.getSingletonTarget(studentController);//获取被代理的原始对象
    }
}

4、运行结果及分析

5、程序Debug过程中的对象详情

从Debug信息可以看出:

  • excution表达式包含的类,通过ApplicationContext.getBean方法获取的对象都是代理对象(studentDao和studentController对象),其中studentDao 实现了接口,所以是jdk的动态代理对象,studentController没有实现接口,是CGLib组件生成的代理对象。没有被excution表达式包含的类,如studentService对象,ApplicationContext.getBean方法获取的对象就是原始类型的对象
  • 通过Advised.getTargetSource().getTarget()和AopProxyUtils.getSingletonTarget都可以获取被代理的目标对象,从程序看出,被代理的目标对象都是原始类型,并且被代理对象是同一个,内存地址都相同

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

One-Class Classification: A Survey

I. INTRODUCTION 1.定义 OCC 是一种特殊的多类分类,训练数据仅来自单个正类。目标是学习表示和/或分类器,以便在推理过程中识别正类查询。 2.应用 异常图像检测、异常事件检测、生物识别(活体检测、反诈骗) 3.与其他领域的比…

Python连接Kafka收发数据等操作

目录 一、Kafka 二、发送端(生产者) 三、接收端(消费者) 四、其他操作 一、Kafka Apache Kafka 是一个开源流处理平台,由 LinkedIn 开发,并于 2011 年成为 Apache 软件基金会的一部分。Kafka 广泛用于构…

在Java中,关于final、static关键字与方法的重写和继承【易错点】

在Java中,关于final、static关键字与方法的重写和继承【易错点】 1.final方法不能被重写2.static方法不是重写,而是遮蔽3.final与static的组合4.final与继承5.static与继承 1.final方法不能被重写 如果父类中的方法被声明为final,那么这个方法…

开源音频处理项目推荐【持续更新】

Audacity 介绍:Audacity是一款功能强大的开源音频编辑软件,适用于多种操作系统,包括Windows、macOS和Linux。它支持多轨音频编辑、录制,并且提供了丰富的音频处理功能,如剪切、复制、粘贴、混音、降噪等 。Audacity的…

基于Python+flask+MySQL+HTML的全国范围水质分析预测系统,可视化用echarts,预测算法随机森林

1绪论 近年来,水质监测系统的进步显著,这在全球环保意识不断提升的背景下尤为明显。大量资源被投入到水质监测技术的研发和应用中,以不断优化监测效能。水资源的保护及健康环境的维护,这种趋势旨在提升人们生活质量,确…

微软宣称其新工具可纠正人工智能幻觉 但专家依然对此表示怀疑

人工智能经常胡言乱语,微软现在说它有办法解决这个问题,但我们有理由对此持怀疑态度。微软今天发布了一项名为"更正"(Correction)的服务,它可以自动修改人工智能生成的与事实不符的文本。Correction 首先会标…

华为认证HCIA篇--网络通信基础

大家好呀!我是reload。今天来带大家学习一下华为认证ia篇的网络通信基础部分,偏重一些基础的认识和概念性的东西。如果对网络通信熟悉的小伙伴可以选择跳过,如果是新手或小白的话建议还是看一看,先有个印象,好为后续的…

8.隐私与安全 - 使用ChatGPT时的注意事项【8/10】

引言 在数字时代,隐私和安全已成为全球关注的焦点。随着技术的发展,个人信息和数据的收集、存储、处理和传输变得越来越普遍,这既带来了便利,也带来了风险。保护个人隐私和数据安全不仅是法律的要求,也是维护公众信任…

solidwork中查看装配体螺丝或零件

假设我的PETG打印件到了,想知道这个螺丝的型号,怎么办 解决办法: 第一步先看看有没有固定的字样 如果固定的话是不行的。需要这样做: 把这里给关了 接下来第二步,点击你想查看的螺丝 然后就会跳到零件图 可以看到直径…

Cloudflare为网站添加AI审计 可检查AI爬虫何时抓取和抓取频次以及直接屏蔽爬虫

网络服务提供商 Cloudflare 宣布即日起为所有网站 (包括免费托管的网站) 带来 AI 审计功能,该功能目前处于测试阶段,可以分析 AI 公司的爬虫和抓爬数据。新的 AI 审计工具 (Cloudflare AI Audit) 主要提供 AI 公司的爬虫何时到网站来抓取数据、抓取的数据…

Unity 热更新(HybridCLR+Addressable)-资源更新

七、资源更新 创建一个叫Aot的文件夹,用来存放不会热更新的资源 这个修改为第三个 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b8be5e6465184ad5ad6173c6870bfa06.png 这个是更新 在更新或者打包时遇到端口被占的报错,不用理会&#xf…

二、认识大模型

认识大模型 什么是大模型?发展趋势AGI是不是泡沫大模型对比【时效】大模型特点大模型技术原理向量化除了向量化,大模型还具有特征提取特点 总结结语 什么是大模型? 大模型是大规模语言模型(Large Language Model)的简…

mysql如何替换数据库所有表中某些字段含有的特定值

目录 背景查询所有表名查询表的所有字段过虑特征字段替换字段中含有的特定值 背景 公司的测试域名更换了,导致存放在数据库中的域名也要跟着替换,当然把域名存放在数据库表中是不科学的,不建议这样做,但公司的同事就这样做了&…

由动静压之比求马赫数的MATLAB函数

函数介绍 输入:动静压之比 p r e pre pre 输出:马赫数 M a c h Mach Mach 【注】仅适合亚音速的情况,如果动静压之比过大或过小,会有相应的提示 函数源代码 function [m] pre2mach(pre) m(5*(pre1).^0.2857-5).^0.5; if pre&l…

Leetcode 螺旋矩阵

算法思想: 这个算法的目标是按照顺时针螺旋的顺序从矩阵中取出元素。为了做到这一点,整个思路可以分成几个关键步骤: 定义边界:首先需要定义四个边界变量: left:当前左边界的索引。right:当前右…

uniapp 实现3d轮播图,也就是中间的放大两边的缩小 用swiper和swiper-item就能实现

话不多说&#xff0c;直接上代码&#xff0c;无需引入外部资源&#xff0c; 用swiper和swiper-item就能实现 先上结构代码 <swiper class"header" circular previous-margin"80rpx" next-margin"60rpx" :current"current"change&…

点亮城市安全:高科技助力精准定位路灯漏电‘隐形杀手

在城市的每一个角落&#xff0c;路灯如同守夜人&#xff0c;默默照亮归家的路。然而&#xff0c;当这些守护者出现“漏电”隐患时&#xff0c;不仅威胁着行人的安全&#xff0c;还可能引发一系列电气故障。那么&#xff0c;如何精准快速地找出这些隐藏的漏电点&#xff0c;并有…

二叉树进阶oj题【二叉树相关10道oj题的解析和代码实现】

目录 二叉树进阶oj题1.根据二叉树创建字符串2.二叉树的层序遍历3.二叉树的层序遍历 II4.二叉树的最近公共祖先5.二叉搜索树和双向链表6.从前序与中序遍历序列构造二叉树7.从中序和后序遍历序列来构造二叉树8.二叉树的前序遍历&#xff0c;非递归迭代实现9.二叉树中序遍历 &…

防止电脑电池老化,禁止usb或者ac接口调试时充电

控制android系统&#xff0c;开发者模式&#xff0c;开启和禁止充电 连接 Android 手机到电脑的 USB 端口。 下载并安装 Android Debug Bridge (ADB) 工具[1]。 USB&#xff1a; 在命令行中输入 adb shell dumpsys battery set usb 0&#xff0c;以禁止 USB 充电。 在命令…

【AI创作组】Matlab中进行符号计算

提示:代码一定要自己运行过才算数…… 1. 符号计算工具箱介绍 1.1 工具箱功能 MATLAB的符号计算工具箱,即Symbolic Math Toolbox,是一套强大的数学软件工具,它使得MATLAB具备了符号运算的能力。该工具箱提供了一系列函数,用于求解、绘制和操作符号数学方程。用户可以直接…