首页 > 其他分享 >设计模式之代理,手动实现动态代理,揭秘原理实现

设计模式之代理,手动实现动态代理,揭秘原理实现

时间:2022-11-03 10:00:12浏览次数:113  
标签:java STR 代理 TAB ENTER 设计模式 揭秘 append

开心一刻

  周末,带着老婆儿子一起逛公园。儿子一个人跑在前面,吧唧一下不小心摔了一跤,脑袋瓜子摔了个包,稀里哗啦的哭道:“爸爸,我会不会摔成傻子!”

  我指了指我头上的伤痕安慰道:“不会的,你看,这是爸爸小时候摔的。”

  话还没有说话,小家伙哭的更厉害了:“那就是说我长大后就会和你一样傻了,我不要,我不要!”

  老婆忍不住发飙:“别哭了,你怎么会变傻呢?你看你爸,你爸傻吗?”

  我赶紧回应道:“是啊,你看我多聪明!”

  儿子:“真的,不骗我?”

  老婆:“当然!”

  儿子:“可是如果老爸不是傻子,当年怎么会娶你这个母老虎呢?”

  我、老婆:……

设计模式之代理,手动实现动态代理,揭秘原理实现_动态代理

什么是代理模式

  所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的左右。

  代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问,通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。说简单点,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。一般而言会分三种:静态代理、动态代理和CGLIB代理

  代理模式结构如下:

设计模式之代理,手动实现动态代理,揭秘原理实现_动态代理_02

静态代理

  静态代理需要代理对象和被代理对象实现一样的接口,我们来看个例子就清楚了

  示例代理:​​static-proxy​

  代理类:UserDaoProxy.java

/**
* 代理逻辑在代理类中,而不是由用户自定义
*/
public class UserDaoProxy implements IUserDao {

private IUserDao target; // 被代理对象

public UserDaoProxy(IUserDao target) {
this.target = target;
}

/**
* 前置/后置 处理一旦写完,就固定死了,后续想修改的话需要改此代理类
* @param id
* @return
*/
public int delete(int id) {
// 前置处理,例如开启事务
System.out.println("前置处理...");

// 调用目标对象方法
int count = target.delete(id);

// 后置处理,例如提交事务或事务回滚
System.out.println("前置处理...");
return count;
}
}

View Code

  UserDaoProxy代理IUserDao类型,此时也只能代理IUserDao类型的被代理对象。测试结果就不展示了,相信大家看了代码也知道了

  优点:可以在不修改目标对象的前提下扩展目标对象的功能

  缺点:如果需要代理多个类,每个类都会有一个代理类,会导致代理类无限制扩展;如果类中有多个方法,同样的代理逻辑需要反复实现、应用到每个方法上,一旦接口增加方法,目标对象与代理对象都要进行修改

  一个静态代理只能代理一个类,那么有没有什么方式可以实现同一个代理类来代理任意对象呢?肯定有的,也就是下面讲到的:动态代理

动态代理

  代理类在程序运行时创建的代理方式被成为动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。下面我们一步一步手动来实现动态代理。下面的示例都是直接针对接口的,就不是针对接口的具体实现类了,静态代理示例中,UserDaoProxy代理的是IUserDao的实现类:UserDaoImpl,那么动态代理示例就直接针对接口了,下面示例针对的都是UserMapper接口,模拟的mybatis,但不局限于UserMapper接口

  代理类源代码持久化

    1、先利用反射动态生成代理类,并持久化代理类到磁盘(也就是生成代理类的java源文件),generateJavaFile方法如下

/**
* 生成接口实现类的源代码, 并持久化到java文件
* @param interface_
* @param proxyJavaFileDir
* @throws Exception
*/
private static void generateJavaFile(Class<?> interface_, String proxyJavaFileDir) throws Exception {
StringBuilder proxyJava = new StringBuilder();
proxyJava.append("package ").append(interface_.getPackage().getName()).append(";").append(ENTER).append(ENTER)
.append("public class ").append(PROXY_CLASS_NAME).append(" implements ").append(interface_.getName()).append(" {");
Method[] methods = interface_.getMethods();
for(Method method : methods) {
Type returnType = method.getGenericReturnType();
Type[] paramTypes = method.getGenericParameterTypes();
proxyJava.append(ENTER).append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
.append(TAB_STR).append("public ").append(returnType.getTypeName()).append(" ").append(method.getName()).append("(");
for(int i=0; i<paramTypes.length; i++) {
if (i != 0) {
proxyJava.append(", ");
}
proxyJava.append(paramTypes[i].getTypeName()).append(" param").append(i);
}
proxyJava.append(") {").append(ENTER)
.append(TAB_STR).append(TAB_STR)
.append("System.out.println(\"数据库操作, 并获取执行结果...\");").append(ENTER); // 真正数据库操作,会有返回值,下面的return返回应该是此返回值
if (!"void".equals(returnType.getTypeName())) {
proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER); // 这里的"null"应该是上述中操作数据库后的返回值,为了演示写成了null
}
proxyJava.append(TAB_STR).append("}").append(ENTER);
}
proxyJava .append("}");

// 写入文件
File f = new File(proxyJavaFileDir + PROXY_CLASS_NAME + ".java");
FileWriter fw = new FileWriter(f);
fw.write(proxyJava.toString());
fw.flush();
fw.close();
}

View Code

      生成的代理类:$Proxy0.java 如下

package com.lee.mapper;

public class $Proxy0 implements com.lee.mapper.UserMapper {

@Override
public java.lang.Integer save(com.lee.model.User param0) {
System.out.println("数据库操作, 并获取执行结果...");
return null;
}


@Override
public com.lee.model.User getUserById(java.lang.Integer param0) {
System.out.println("数据库操作, 并获取执行结果...");
return null;
}
}

View Code

      这个代理类的生成过程是我们自己实现的,实现不难,但排版太繁琐,我们可以用​​javapoet​​来生成代理类源代码,generateJavaFileByJavaPoet方法如下

/**
* 用JavaPoet生成接口实现类的源代码,并持久化到java文件
* @param interface_ 目标接口类
* @return
*/
public static void generateJavaFileByJavaPoet(Class<?> interface_) throws Exception {

// 类名、实现的接口,以及类访问限定符
TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("JavaPoet$Proxy0")
.addSuperinterface(interface_)
.addModifiers(Modifier.PUBLIC);

Method[] methods = interface_.getDeclaredMethods();
for (Method method : methods) {

// 方法参数列表
List<ParameterSpec> paramList = new ArrayList<>();
Type[] paramTypes = method.getGenericParameterTypes();
int count = 1 ;
for (Type param : paramTypes) {
ParameterSpec paramSpec = ParameterSpec.builder(Class.forName(param.getTypeName()), "param" + count)
.build();
count ++;
paramList.add(paramSpec);
}

// 方法名、方法访问限定符、参数列表、返回值、方法体等
Class<?> returnType = method.getReturnType();
MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getName())
.addModifiers(Modifier.PUBLIC)
.addParameters(paramList)
.addAnnotation(Override.class)
.returns(returnType)
.addCode("\n")
.addStatement("$T.out.println(\"数据库操作, 并获取执行结果...\")", System.class) // 真正数据库操作,会有返回值,下面的return返回应该是此返回值
.addCode("\n");
if (!"void".equals(returnType.getName())) {
builder.addStatement("return null"); // 这里的"null"应该是上述中操作数据库后的返回值,为了演示写成了null
}

MethodSpec methodSpec = builder.build();
typeSpecBuilder.addMethod(methodSpec);

}

JavaFile javaFile = JavaFile.builder(interface_.getPackage().getName(), typeSpecBuilder.build()).build();
javaFile.writeTo(new File(SRC_JAVA_PATH));
}

View Code

      生成的代理类:JavaPoet$Proxy0.java 如下

package com.lee.mapper;

import com.lee.model.User;
import java.lang.Integer;
import java.lang.Override;
import java.lang.System;

public class JavaPoet$Proxy0 implements UserMapper {
@Override
public Integer save(User param1) {

System.out.println("数据库操作, 并获取执行结果...");

return null;
}

@Override
public User getUserById(Integer param1) {

System.out.println("数据库操作, 并获取执行结果...");

return null;
}
}

View Code

    利用javapoet生成的代理类更接*我们*时手动实现的类,排版更符合我们的编码习惯,看上去更自然一些;两者的实现过程是一样的,只是javapoet排版更好

    2、既然代理类的源代码已经有了,那么需要对其编译了,compileJavaFile方法如下

/**
* 编译代理类源代码生成代理类class
* @param proxyJavaFileDir
*/
private static void compileJavaFile(String proxyJavaFileDir) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//获得文件管理者
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> fileObjects = manager.getJavaFileObjects(proxyJavaFileDir + PROXY_CLASS_NAME + ".java");
//编译任务
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, fileObjects);
//开始编译,执行完可在指定目录下看到.class文件
task.call();
//关闭文件管理者
manager.close();
}

View Code

      会在指定目录下看到:$Proxy0.class

    3、加载$Proxy0.class,并创建其实例对象(代理实例对象)

public static <T> T newInstance(Class<T> interface_) throws Exception{
String proxyJavaFileDir = SRC_JAVA_PATH + interface_.getPackage().getName().replace(".", File.separator) + File.separator;

// 1、生成interface_接口的实现类,并持久化到磁盘:$Proxy0.java
generateJavaFile(interface_, proxyJavaFileDir);

// 2、编译$Proxy0.java,生成$Proxy0.class到磁盘
compileJavaFile(proxyJavaFileDir);

// 3、加载$Proxy0.class,并创建其实例对象(代理实例对象)
MyClassLoader loader = new MyClassLoader(proxyJavaFileDir, interface_);
Class<?> $Proxy0 = loader.findClass(PROXY_CLASS_NAME);
return (T)$Proxy0.newInstance();
}

private static class MyClassLoader<T> extends ClassLoader {

private String proxyJavaFileDir;
private Class<T> interface_;

public MyClassLoader(String proxyJavaFileDir, Class<T> interface_) {
this.proxyJavaFileDir = proxyJavaFileDir;
this.interface_ = interface_;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {

File clazzFile = new File(proxyJavaFileDir, name + ".class");
//如果字节码文件存在
if (clazzFile.exists()) {
//把字节码文件加载到VM
try {
//文件流对接class文件
FileInputStream inputStream = new FileInputStream(clazzFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len); // 将buffer中的内容读取到baos中的buffer
}
//将buffer中的字节读到内存加载为class
return defineClass(interface_.getPackage().getName() + "." + name, baos.toByteArray(), 0, baos.size());
} catch (Exception e) {
e.printStackTrace();
}
}
return super.findClass(name);
}
}

View Code

      有了代理实例对象,我们就可以利用它进行操作了,演示结果如下

设计模式之代理,手动实现动态代理,揭秘原理实现_动态代理_03

    完整工程地址:​​proxy-java-file​​,完整流程图如下

设计模式之代理,手动实现动态代理,揭秘原理实现_手动模拟_04

    此时的Proxy类能创建任何接口的实例,解决了静态代理存在的代理类泛滥、多个方法中代理逻辑反复实现的问题;但有个问题不知道大家注意到:$Proxy0.java有必要持久化到磁盘吗,我们能不能直接编译内存中的代理类的字符串源代码,得到$Proxy0.class呢?

  代理类源代码不持久化

不用写、读磁盘,可以提升不少性能

    完整工程地址:​​proxy-none-java-file​​,此时的流程图如下

设计模式之代理,手动实现动态代理,揭秘原理实现_手动模拟_05

    Proxy.java源代码如下

package com.lee.proxy;

import com.itranswarp.compiler.JavaStringCompiler;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;

public class Proxy {

private static final String ENTER = "\r\n";
private static final String TAB_STR = " ";
private static final String PROXY_FILE_NAME = "$Proxy0";

/**
* 生成接口实现类的源代码, 并持久化到java文件
* @param interface_
* @throws Exception
*/
private static String generateJavaFile(Class<?> interface_) throws Exception {
StringBuilder proxyJava = new StringBuilder();
proxyJava.append("package ").append(interface_.getPackage().getName()).append(";").append(ENTER).append(ENTER)
.append("public class ").append(PROXY_FILE_NAME).append(" implements ").append(interface_.getName()).append(" {");
Method[] methods = interface_.getMethods();
for(Method method : methods) {
Type returnType = method.getGenericReturnType();
Type[] paramTypes = method.getGenericParameterTypes();
proxyJava.append(ENTER).append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
.append(TAB_STR).append("public ").append(returnType.getTypeName()).append(" ").append(method.getName()).append("(");
for(int i=0; i<paramTypes.length; i++) {
if (i != 0) {
proxyJava.append(", ");
}
proxyJava.append(paramTypes[i].getTypeName()).append(" param").append(i);
}
proxyJava.append(") {").append(ENTER)
.append(TAB_STR).append(TAB_STR)
.append("System.out.println(\"数据库操作, 并获取执行结果...\");").append(ENTER); // 真正数据库操作,会有返回值,下面的return返回应该是此返回值
if (!"void".equals(returnType.getTypeName())) {
proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER); // 这里的"null"应该是上述中操作数据库后的返回值,为了演示写成了null
}
proxyJava.append(TAB_STR).append("}").append(ENTER);
}
proxyJava .append("}");

return proxyJava.toString();
}

private final static Class<?> compile(String className, String content) throws Exception {
JavaStringCompiler compiler = new JavaStringCompiler();
Map<String, byte[]> byteMap = compiler.compile(PROXY_FILE_NAME + ".java", content);
Class<?> clazz = compiler.loadClass(className, byteMap);
return clazz;
}

public static <T> T newInstance(Class<T> interface_) throws Exception{

// 1、生成源代码字符串
String proxyCodeStr = generateJavaFile(interface_);

// 2、字符串编译成Class对象
Class<?> clz = compile(interface_.getPackage().getName() + "." + PROXY_FILE_NAME, proxyCodeStr);
return (T)clz.newInstance();
}
}

View Code

    相比​​有代理类源代码持久化​​,核心的动态代理生成过程不变,只是减少了.java和.class文件的持久化;其中用到了第三方工具:com.itranswarp.compile(我们也可以拓展jdk,实现内存中操作),完成了字符串在内存中的编译、class在内存中的加载,直接用jdk的编译工具,会在磁盘生成$Proxy0.class

    测试结果如下

设计模式之代理,手动实现动态代理,揭秘原理实现_java_06

      可以看到,没有.java和.class的持久化

    此时就完美了吗?如果现在有另外一个接口ISendMessage,代理逻辑不是

System.out.println("数据库操作, 并获取执行结果...")

    我们该怎么办? 针对ISendMessage又重新写一个Proxy?显然还不够灵活,说的简单点:此种代理可以代理任何接口,但是代理逻辑确是固定死的,不能自定义,这样会造成一种代理逻辑会有一个代理工厂(Proxy),会造成代理工厂的泛滥

  代理逻辑接口化,供用户自定义

    既然​​无代理类源代码持久化中​​的代理逻辑不能自定义,那么我们就将它抽出来,提供代理逻辑接口

    完整工程地址:​​proxy-none-java-file-plus​​,流程图与​​无代理类源代码持久化中​​一样,此时代理类的生成过程复杂了不少,涉及到代理逻辑接口:InvacationHandler的处理

    generateJavaFile(...)方法

/**
* 生成接口实现类的源代码
* @param interface_
* @throws Exception
*/
private static String generateJavaFile(Class<?> interface_, InvocationHandler handler) throws Exception {
StringBuilder proxyJava = new StringBuilder();
proxyJava.append("package ").append(PROXY_PACKAGE_NAME).append(";").append(ENTER).append(ENTER)
.append("import java.lang.reflect.Method;").append(ENTER).append(ENTER)
.append("public class ").append(PROXY_FILE_NAME).append(" implements ").append(interface_.getName()).append(" {").append(ENTER)
.append(ENTER).append(TAB_STR).append("private InvocationHandler handler;").append(ENTER).append(ENTER);

// 代理对象构造方法
proxyJava.append(TAB_STR).append("public ").append(PROXY_FILE_NAME).append("(InvocationHandler handler) {").append(ENTER)
.append(TAB_STR).append(TAB_STR).append("this.handler = handler;").append(ENTER)
.append(TAB_STR).append("}").append(ENTER);

// 接口方法
Method[] methods = interface_.getMethods();
for(Method method : methods) {
String returnTypeName = method.getGenericReturnType().getTypeName();
Type[] paramTypes = method.getGenericParameterTypes();
proxyJava.append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
.append(TAB_STR).append("public ").append(returnTypeName).append(" ").append(method.getName()).append("(");

List<String> paramList = new ArrayList<>(); // 方法参数值
List<String> paramTypeList = new ArrayList<>(); // 方法参数类型
for(int i=0; i<paramTypes.length; i++) {
if (i != 0) {
proxyJava.append(", ");
}
String typeName = paramTypes[i].getTypeName();
proxyJava.append(typeName).append(" param").append(i);
paramList.add("param" + i);
paramTypeList.add(typeName+".class");
}
proxyJava.append(") {").append(ENTER)
.append(TAB_STR).append(TAB_STR).append("try {").append(ENTER)
.append(TAB_STR).append(TAB_STR).append(TAB_STR)
.append("Method method = ").append(interface_.getName()).append(".class.getDeclaredMethod(\"")
.append(method.getName()).append("\",").append(String.join(",", paramTypeList)).append(");")
.append(ENTER).append(TAB_STR).append(TAB_STR).append(TAB_STR);
if (!"void".equals(returnTypeName)) {
proxyJava.append("return (").append(returnTypeName).append(")");
}
proxyJava.append("handler.invoke(this, method, new Object[]{")
.append(String.join(",", paramList)).append("});").append(ENTER)
.append(TAB_STR).append(TAB_STR).append("} catch(Exception e) {").append(ENTER)
.append(TAB_STR).append(TAB_STR).append(TAB_STR).append("e.printStackTrace();").append(ENTER)
.append(TAB_STR).append(TAB_STR).append("}").append(ENTER);
if (!"void".equals(returnTypeName)) {
proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER);
}
proxyJava.append(TAB_STR).append("}").append(ENTER);
}
proxyJava .append("}");

// 这里可以将字符串生成java文件,看看源代码对不对
/*String proxyJavaFileDir = System.getProperty("user.dir") + File.separator + "proxy-none-java-file-plus"
+ String.join(File.separator, new String[]{"","src","main","java",""})
+ PROXY_PACKAGE_NAME.replace(".", File.separator) + File.separator;
File f = new File(proxyJavaFileDir + PROXY_FILE_NAME + ".java");
FileWriter fw = new FileWriter(f);
fw.write(proxyJava.toString());
fw.flush();
fw.close();*/

return proxyJava.toString();
}

View Code

    测试结果如下

设计模式之代理,手动实现动态代理,揭秘原理实现_动态代理_07

    此时各组件之间关系、调用情况如下

设计模式之代理,手动实现动态代理,揭秘原理实现_手动模拟_08

    此时Proxy就可以完全通用了,可以生成任何接口的代理对象了,也可以实现任意的代理逻辑;至此,我们完成了一个简易的仿JDK实现的动态代理

  JDK的动态代理

    我们来看看JDK下动态代理的实现,示例工程:​​proxy-jdk​​,测试结果就不展示了,我们来看看JDK下Proxy.newInstance方法,有三个参数

      1、Classloader:类加载器,我们可以使用自定义的类加载器;上述手动实现示例中,直接在Proxy写死了;

      2、Class<?>[]:接口类数组,这个其实很容易理解,我们应该允许我们自己实现的代理类同时实现多个接口。我们上述手动实现中只传入一个接口,是为了简化实现;

      3、InvocationHandler:这个没什么好说的,与我们的实现一致,用于自定义代理逻辑

    我们来追下源码,看看JDK的动态代理是否与我们的手动实现是否一致

设计模式之代理,手动实现动态代理,揭秘原理实现_手动模拟_09

    与我们的自定义实现差不多,利用反射,逐个接口、逐个方法进行处理;ProxyClassFactory负责生成代理类的Class对象,主要由apply方法负责,调用了

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

    来生成代理类的Class;ProxyGenerator中有个是有静态常量:saveGeneratedFiles,标识是否持久化代理类的class文件,默认值是false,也就是不持久化,我们可以通过设置jdk系统参数,实现JDK的动态代理持久化代理类的class文件

CGLIB代理

  对cglib不做深入研究了,只举个使用案例:​​proxy-cglib​​,使用方式与JDK的动态代理类似,实现的效果也基本一致,但是实现原理上还是有差别的

  JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,而CGLIB没有这个限制,具体区别不是本文范畴了,大家自行去查阅资料

应用场景

  长篇大论讲了那么多,我们却一直没有讲动态代理的作用,使用动态代理我们可以在不改变源码的情况下,对目标对象的目标方法进行前置或后置增强处理。这有点不太符合我们的一条线走到底的编程逻辑,这种编程模型有一个专业名称叫AOP,面向切面编程,具体案例有如下:

  1、spring的事务,事务的开启可以作为前置增强,事务的提交或回滚作为后置增强,数据库的操作处在两者之间(目标对象需要完成的事);

  2、日志记录,我们可以在不改变原有实现的基础上,对目标对象进行日志的输出,可以前置处理,记录参数情况,也可以后置处理,记录返回的结果;

  3、web编程,传入参数的校验;

  4、web编程,权限的控制也可以用aop来实现;

  只要明白了AOP,那么哪些场景能使用动态代理也就比较明了了

总结

  1、示例代码中的Proxy是代理工厂,负责生产代理对象的,不是代理对象类

  2、手动实现动态代理,我们分了三版

    第一版:代理类源代码持久化,为了便于理解,我们将代理类的java文件和class文件持久化到了磁盘,此时解决了静态代理中代理类泛滥的问题,我们的代理类工厂(Proxy)能代理任何接口;

    第二版:代理类源代码不持久化,代理类的java文件和和class文件本来就只是临时文件,将其去掉,不用读写磁盘,可以提高效率;但此时有个问题,我们的代理逻辑却写死了,也就说一个代理类工厂只能生产一种代理逻辑的代理类对象,如果我们有多种代理逻辑,那么就需要有多个代理类工厂,显然灵活性不够高,还有优化空间;

    第三版:代理逻辑接口化,供用户自定义,此时代理类工厂就可以代理任何接口、任何代理逻辑了,反正代理逻辑是用户自定义传入,用户想怎么定义就怎么定义;

  3、示例参考的是mybatis中mapper的生成过程,虽然只是简单的模拟,但流程却是一致的,有兴趣的可以看看我前两篇博客,结合起来看更好理解

参考

  《java与模式》

  ​​10分钟看懂动态代理设计模式​​



标签:java,STR,代理,TAB,ENTER,设计模式,揭秘,append
From: https://blog.51cto.com/u_13423706/5818626

相关文章

  • 设计模式十三(数据访问对象模式,前端控制器模式)
    数据访问对象模式(DataAccessObjectPattern)或DAO模式用于把低级的数据访问API或操作从高级的业务服务中分离出来。以下是数据访问对象模式的参与者。数据访问对象......
  • 设计模式之代理模式
    概述代理模式是一种应用很广泛的结构型设计模式,而且变化很多。在代理模式中引入了一个新的代理对象,代理对象可以在客户端对象和目标对象之间起到中介的作用,去掉客户不能看......
  • windows完全代理配置(包括UWP,应用商店,系统更新),所有应用走代理
    部分参考自知乎基本浏览器代理首先基本的浏览器代理设置,照常使用rayN等代理软件即可。建议在设置里启用局域网连接,特殊操作后可以通过热点共享代理。引用环境变量的......
  • 1-设计模式介绍
    1,设计模式概述1.1软件设计模式的产生背景"设计模式"最初被用于建筑领域的设计中。1977年美国著名建筑大师`克里斯托夫·亚历山大在他的著作《建筑模式语言:城镇、建筑、......
  • 设计模式---策略模式
    简述预先定义有着不同执行过程但结果相同的算法族,运行时指定所需算法。算法族此处为一组有共同主题的有相同结果的不同算法的集合。话不多说,看个优化案例。优化案例......
  • day 24设计模式
    概述:设计模式是一种固定的解决某个问题的一种方式,不区分语言,常用的设计模式有23种,分为三大类(针对类和对象)设计模式分类创建型(创建对象)单例模式、工厂模式结构型(将多个......
  • 设计模式十二(访问者模式,mvc模式,业务代表模式)
    访问者模式    publicinterfaceComputerPart{publicvoidaccept(ComputerPartVisitorcomputerPartVisitor);}publicclassMonitorimplementsC......
  • Jenkins Pipeline 流水线 - 使用代理节点,Remote SSH 对 K8S 进行升级
    JenkinsPipeline流水线-使用代理节点、RemoteSSH进行K8Skubectl升级RemoteSSH方式安装插件SSHPipelineStepsPipelineSSH脚本credentialsId:'K8SMa......
  • 设计模式十一(空对象模式,策略模式、模板模式)
    在空对象模式(NullObjectPattern)中,一个空对象取代NULL对象实例的检查。Null对象不是检查空值,而是反应一个不做任何动作的关系。这样的Null对象也可以在数据不可用的......
  • 如何确保独享HTTP代理池不被泄露
    我们在很多时候,使用提取式HTTP代理,用过一段时间之后,就会觉得有时候提取的IP数量不够,或者根本提取不到IP,出现这种情况,很大可能是因为代理池被人盗用。那么如何解决这种......