【设计模式】代理模式

news/2024/12/2 16:35:08/

【设计模式】代理模式

  • 1.代理模式介绍
  • 2. 🌰
    • 2.1 代理模式中间件模型结构
    • 2.2 代码实现
      • Dao层接口
      • 代理类定义
      • 将Bean定义注册到Spring容器
      • 配置⽂件spring-config
      • 测试验证
  • 3. InvocationHandler接口
  • 附录

在这里插入图片描述

1.代理模式介绍

代理模式有点像⽼⼤和⼩弟,也有点像分销商。主要解决的问题是为某些资源的访问、对象的类的易⽤操作上提供⽅便使⽤的代理服务。⽽这种设计思想的模式经常会出现在我们的系统中,或者你⽤到过的组件中,它们都提供给你⼀种⾮常简单易⽤的⽅式控制原本你需要编写很多代码的进⾏使⽤的服务类。

类似这样的场景可以想到;

  1. 你的数据库访问层⾯经常会提供⼀个较为基础的应⽤,以此来减少应⽤服务扩容时不⾄于数据库连接数暴增。
  2. 使⽤过的⼀些中间件例如;RPC框架,在拿到jar包对接⼝的描述后,中间件会在服务启动的时候⽣成对应的代理类,当调⽤接⼝的时候,实际是通过代理类发出的socket信息进⾏通过。
  3. 另外像我们常⽤的 MyBatis ,基本是定义接⼝但是不需要写实现类,就可以对 xml 或者⾃定义注解⾥的 sql 语句进⾏增删改查操作。

2. 🌰

在这里插入图片描述
模拟实现mybatis-spring中代理类⽣成部分
对于Mybatis的使⽤中只需要定义接⼝不需要写实现类就可以完成增删改查操作,有疑问的⼩伙伴,在本章节中就可以学习到这部分知识。解析下来我们会通过实现⼀个这样的代理类交给spring管理的核⼼过程,来讲述代理类模式。

这样的案例场景在实际的业务开发中其实不多,因为这是将这种思想运⽤在中间件开发上,⽽很多⼩伙伴经常是做业务开发,所以对Spring的bean定义以及注册和对代理以及反射调⽤的知识了解的相对较少。但可以通过本章节作为⼀个⼊⻔学习,逐步了解。

2.1 代理模式中间件模型结构

在这里插入图片描述

  • 此模型中涉及的类并不多,但都是抽离出来的核⼼处理类。主要的事情就是对类的代理和注册到spring中。
  • 上图中最上⾯是关于中间件的实现部分,下⾯对应的是功能的使⽤。

2.2 代码实现

在这里插入图片描述

  • 这⾥我们定义了⼀个模拟mybatis-spring中的⾃定义注解,⽤于使⽤在⽅法层⾯。
import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {String value() default "";
}

Dao层接口

import org.itstack.demo.design.agent.Select;public interface IUserDao {@Select("select userName from user where id = #{uId}")String queryUserInfo(String uId);
}

代理类定义

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class MapperFactoryBean<T> implements FactoryBean<T> {private Logger logger = LoggerFactory.getLogger(MapperFactoryBean.class);private Class<T> mapperInterface;public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic T getObject() throws Exception {InvocationHandler handler = (proxy, method, args) -> {Select select = method.getAnnotation(Select.class);logger.info("SQL:{}", select.value().replace("#{uId}", args[0].toString()));return args[0] + ",小傅哥,bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!";};return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);}@Overridepublic Class<?> getObjectType() {return mapperInterface;}@Overridepublic boolean isSingleton() {return true;}
}
  • 如果你有阅读过mybatis源码,是可以看到这样的⼀个类; MapperFactoryBean ,这⾥我们也模
    拟⼀个这样的类,在⾥⾯实现我们对代理类的定义。
  • 通过继承 FactoryBean ,提供bean对象,也就是⽅法; T getObject() 。
  • 在⽅法 getObject() 中提供类的代理以及模拟对sql语句的处理,这⾥包含了⽤户调⽤dao层⽅法
    时候的处理逻辑。
  • 还有最上⾯我们提供构造函数来透传需要被代理类, Class mapperInterface ,在mybatis
    中也是使⽤这样的⽅式进⾏透传。
  • 另外 getObjectType() 提供对象类型反馈,以及 isSingleton() 返回类是单例的。

将Bean定义注册到Spring容器

import org.itstack.demo.design.IUserDao;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(MapperFactoryBean.class);beanDefinition.setScope("singleton");beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(IUserDao.class);BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {// left intentionally blank}
}
  • 这⾥我们将代理的bean交给spring容器管理,也就可以⾮常⽅便让我们可以获取到代理的bean。
    这部分是spring中关于⼀个bean注册过程的源码。
  • GenericBeanDefinition ,⽤于定义⼀个bean的基本信息 setBeanClass(MapperFactoryBean.class); ,也包括可以透传给构造函数信息 addGenericArgumentValue(IUserDao.class);
  • 最后使⽤ BeanDefinitionReaderUtils.registerBeanDefinition ,进⾏bean的注册,也就
    是注册到 DefaultListableBeanFactory 中。

配置⽂件spring-config

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"default-autowire="byName"><bean id="userDao" class="org.itstack.demo.design.agent.RegisterBeanFactory"/>
</beans>
  • 接下来在配置⽂件中添加我们的bean配置,在mybatis的使⽤中⼀般会配置扫描的dao层包,这样
    就可以减少这部分的配置。

测试验证

 @Testpublic void test_IUserDao() {BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);String res = userDao.queryUserInfo("100001");logger.info("测试结果:{}", res);}

在这里插入图片描述

  • 测试的过程⽐较简单,通过加载Bean⼯⼚获取我们的代理类的实例对象,之后调⽤⽅法返回结
    果。
  • 那么这个过程你可以看到我们是没有对接⼝先⼀个实现类的,⽽是使⽤代理的⽅式给接⼝⽣成⼀个
    实现类,并交给spring管理。

3. InvocationHandler接口

InvocationHandler是Java中的一个接口,它通常是与动态代理一起使用的。在动态代理模式中,InvocationHandler接口的实现类充当代理对象方法调用的处理器,它会拦截代理对象的所有方法调用并执行自定义的逻辑。

package com.company.reflect.InvocationHandler;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author fan* @version 1.0* @date 2023/5/28*/
public class ProxyDemo {public static void main(String[] args) {// 创建一个实现了InvocationHandler接口的类InvocationHandler handler = new MyInvocationHandler();// 创建动态代理对象MyInterface proxy = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class[]{MyInterface.class},handler);// 调用代理对象的方法proxy.sayHello();}
}// 需要代理的接口
interface MyInterface {void sayHello();
}// InvocationHandler的实现类
class MyInvocationHandler implements InvocationHandler {// 实现invoke方法来处理代理对象的方法调用@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before invoking method: " + method.getName());Object result = method.invoke(new MyClass(), args);System.out.println("After invoking method: " + method.getName());return result;}
}// 需要代理的类
class MyClass implements MyInterface {@Overridepublic void sayHello() {System.out.println("Hello World!");}
}/*** 在上面的示例中,我们定义了一个MyInterface接口和一个MyClass类,* 其中MyClass实现了MyInterface接口的方法。* 然后我们创建了一个实现了InvocationHandler接口的MyInvocationHandler类。这个类的invoke方法会在代理对象调用方法时被自动调用。* 在invoke方法中,我们可以编写自己的逻辑来处理代理方法的调用,然后使用Java反射机制来调用被代理对象的方法。** 最后,我们调用Proxy.newProxyInstance方法来创建一个动态代理对象,并将MyInvocationHandler实例传递给它。* 当我们使用代理对象调用sayHello方法时,MyInvocationHandler实例会拦截该方法的调用,并执行自己的逻辑。*/

附录

1.代理模式视频课程
2. 【Java基础】注解–@interface使用详解


http://www.ppmy.cn/news/100364.html

相关文章

python爬虫之人人网模拟登陆示例详解

人人网是中国大陆较早起的社交网站之一&#xff0c;拥有大量的用户。由于随机访问人人网个人主页需要登陆&#xff0c;因此需要使用模拟登陆技术来实现数据的爬取。本文将就人人网的模拟登陆实例进行详细剖析。 获取登陆页面信息 模拟登陆首先需要获取人人网的登陆页面信息&am…

graalvm把java编译为c/c++能够使用的动态库(dll/so)

graalvm把java编译为c/c能够使用的动态库(dll/so) 1.安装graalvm oracle官方企业版 github的openjdk版本 1.1 下载对应系统版本&#xff0c;配置环境变量 本人环境 1. win10 openjdk 17.0.5 2022-10-18 OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.58-jv…

LAMP安装部署

文章目录 一、LAMP平台与编译安装二、安装部署apache服务三、安装部署MySQL四、安装部署php 一、LAMP平台与编译安装 &#xff08;一&#xff09;、LAMP平台概述 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整台系统和相关软件&#xff0c;能够提…

Java的反射机制(3)

目录 类加载 动态加载和静态加载 动态加载和静态加载的区别 类加载时机 类加载的五个阶段 Java反射中可以使用以下方法来获取类的结构信息&#xff1a; 通过反射创建对象 应用案例1 访问属性 访问方法 本章习题 第一题 第二题 类加载 反射机制是java实现动态语言的…

【SpringBoot】整合Mybatis-Plus并输出SQL日志

目录 本地开发环境说明pom.xml主要依赖application.yml主要配置MapperScan注解使用说明实体类示例Mapper接口示例Service接口示例Service接口实现类示例单元测试示例打印SQL日志使用slf4j打印SQL 总结 本地开发环境说明 开发依赖版本Spring Boot3.0.6Mybatis-Plus3.5.3.1JDK20…

组件构建原则

系列文章目录 C高性能优化编程系列 深入理解设计原则系列 深入理解设计模式系列 高级C并发线程编程 组件构建原则 系列文章目录1、组件构建原则的定义和解读 1、组件构建原则的定义和解读 组件构建原则是指在设计和开发组件时遵循的一些基本原则。这些原则旨在确保组件具有高…

Web 自动化测试案例(入门级)——关闭某视频网站弹出广告以及打开登录框输入内容

文章目录 &#x1f4cb;前言&#x1f3af;自动化测试&#x1f9e9;环境的搭建 &#x1f3af;案例介绍&#x1f4dd;最后 &#x1f4cb;前言 人生苦短&#xff0c;我用Python。许久没写博客了&#xff0c;今天又是久违的参与话题的讨论&#xff0c;话题的内容是&#xff1a;如何…

驱动LSM6DS3TR-C实现高效运动检测与数据采集(2)----配置滤波器

工作模式 在LSM6DS3TR-C中&#xff0c;加速度计和陀螺仪可以独立地开启/关闭&#xff0c;并且可以拥有不同的ODR和功耗模式。 LSM6DS3TR-C有三种可用的操作模式&#xff1a; ● 仅加速度计活动&#xff0c;陀螺仪处于断电状态 ● 仅陀螺仪活动&#xff0c;加速度计处于断电状态…