java 设计模式之代理模式
简介
代理模式:使用代理类来增强目标类的功能。在代码结构上,代理对象持有目标对象,通过代理对象访问目标对象,这样可以在不改变目标对象的前提下增加额外的功能,如权限校验、缓存等
代理模式内部的角色:
- 目标类:实现业务功能
- 代理类:用户通过代理类来访问目标类
- 增强方法:代理类中要调用的方法,就是通过它来为目标类添加功能
- 目标接口:代理类和目标类都需要实现目标接口,在有些情况下目标接口是不需要的
代理模式的分类:依据代理类的创建方式,代理模式分为静态代理和动态代理
spring、mybatis等常见框架中大量使用到了动态代理,例如spring的aop,通过动态代理来为目标类增加功能
代理模式的分类
静态代理
静态代理:用户手动编写代理类,在编译时,目标类、代理类已经全部确定
案例:目标类实现售卖功能,代理类中收取服务费
第一步:目标接口
public interface SellTickets {void sell();
}
第二步:目标类
public class TrainStation implements SellTickets {@Overridepublic void sell() {System.out.println("火车站卖票");}
}
第三步:代理类
public class ProxyPoint implements SellTickets {private final TrainStation trainStation; // 代理类持有目标类的实例public ProxyPoint() { }public ProxyPoint(TrainStation trainStation) {this.trainStation = trainStation;}@Overridepublic void sell() {System.out.println("代理点收取服务费");trainStation.sell();}
}
测试:
public class StaticProxyClient {public static void main(String[] args) {// 创建代理类时,传入目标类的实例,通过代理类来访问目标类ProxyPoint proxyPoint = new ProxyPoint(new TrainStation());proxyPoint.sell();}
}
动态代理
动态代理:在程序运行时,基于字节码技术,动态地创建代理类。和静态代理不同的地方在于,静态代理时代理类需要用户手动编写,是编译时生成,动态代理时代理类由程序来自动创建,是运行时生成,用户只需要指定代理类要执行的增强方法和目标类。
用户可以使用jdk原生的方式来创建动态代理类,也可以使用第三方库,例如cglib,提供的方式来创建代理类
基于jdk的动态代理技术
jdk原生的动态代理技术
案例:
第一步:目标接口、目标类,和之前一样
第二步:创建代理类的工厂。代理类是运行时创建的,这里指定代理类的创建方式,程序运行时,使用用户指定的方式,在内存中动态地创建一个类,就是动态代理
public class JdkProxyFactory {// 创建火车站的代理类public static Object getTrainStationProxyObj(TrainStation trainStation) {return dynamicProxy(trainStation,// 代理类中要执行的方法(InvocationHandler) (proxy, method, args) -> { // 这里代理类只拦截指定方法if (method.getName().equals("sell")) { System.out.println("收取代理费");}return method.invoke(trainStation, args); // 执行目标类中的方法});}// 传入目标对象和调用处理器private static <T> Object dynamicProxy(T target, InvocationHandler h) {return Proxy.newProxyInstance(target.getClass().getClassLoader() // 代理类的类加载器, target.getClass().getInterfaces() // 代理类要实现的接口, h);}
}
测试:
public class JdkProxyClient {public static void main(String[] args) {TrainStation targetObj = new TrainStation();SellTickets proxyObj = (SellTickets) JdkProxyFactory.getTrainStationProxyObj(targetObj);proxyObj.sell();}
}
基于cglib的动态代理技术
基于cglib的动态代理技术:由于jdk提供的动态代理技术要求目标类必须要实现目标接口,所以它的使用范围比较小,cglib提供了一种动态代理技术,它是基于继承关系,目标类不需要实现接口。
案例:
第一步:添加依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version>
</dependency>
第二步:目标类,可以没有目标接口,因为cglib提供的动态代理技术,是基于继承关系来进行代理
第三步:创建代理类的工厂
public class CgProxyFactory {public static TrainStation getProxyObj(TrainStation trainStation) {Enhancer enhancer = new Enhancer();// 代理类的父类enhancer.setSuperclass(TrainStation.class);// 代理类中要执行的方法enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)throws Throwable {if (method.getName().equals("sell")) {System.out.println("收取代理费");}return method.invoke(trainStation, objects);}});return (TrainStation) enhancer.create();}
}
测试:
public class CgProxyClient {public static void main(String[] args) {TrainStation targetObj = new TrainStation();TrainStation proxyObj = CgProxyFactory.getProxyObj(targetObj);proxyObj.sell();}
}
原理解析
查看创建出的代理类
使用arthas,查看动态创建出的代理类。具体方法,在程序中打印出代理类的类名,然后程序休眠,使用arthas连接程序,在arthas中使用jad查看类的字节码
案例:
public class CgProxyClient {public static void main(String[] args) {TrainStation targetObj = new TrainStation();TrainStation proxyObj = CgProxyFactory.getProxyObj(targetObj);proxyObj.sell();// 代理类的类名// class org.wyj.proxy.cg_proxy.TrainStation$$EnhancerByCGLIB$$37abd3d3System.out.println("proxyObj.getClass() = " + proxyObj.getClass());// 程序休眠,方便arthus连接try {Thread.sleep(10 * 1000 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}
}
使用arthas连接到当前程序,执行命令,jad ${类的全限定名},查看动态生成的代理类
jdk生成的代理类
案例:这里直接展示创建结果,下面就是之前案例中jdk在内存中动态创建的代理类的字节码,
package com.sun.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.wyj.proxy.jdk_proxy.SellTickets;public final class $Proxy0
extends Proxy
implements SellTickets {private static Method m1;private static Method m4;private static Method m2;private static Method m3;private static Method m0;// 构造方法,调用这个方法来创建代理类的实例public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m4 = Class.forName("org.wyj.proxy.jdk_proxy.SellTickets").getMethod("show", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("org.wyj.proxy.jdk_proxy.SellTickets").getMethod("sell", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void sell() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}
总结:使用jdk创建出的代理类,默认是Proxy的子类,并且实现了用户指定的接口
cglib创建的代理类
案例:之前案例中创建出的代理类
package org.wyj.proxy.cg_proxy;import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.wyj.proxy.cg_proxy.TrainStation;public class TrainStation$$EnhancerByCGLIB$$c8c06447
extends TrainStation
implements Factory {private boolean CGLIB$BOUND;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;private MethodInterceptor CGLIB$CALLBACK_0;private static final Method CGLIB$sell$0$Method;private static final MethodProxy CGLIB$sell$0$Proxy;private static final Object[] CGLIB$emptyArgs;private static final Method CGLIB$finalize$1$Method;private static final MethodProxy CGLIB$finalize$1$Proxy;private static final Method CGLIB$equals$2$Method;private static final MethodProxy CGLIB$equals$2$Proxy;private static final Method CGLIB$toString$3$Method;private static final MethodProxy CGLIB$toString$3$Proxy;private static final Method CGLIB$hashCode$4$Method;private static final MethodProxy CGLIB$hashCode$4$Proxy;private static final Method CGLIB$clone$5$Method;private static final MethodProxy CGLIB$clone$5$Proxy;static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class<?> clazz = Class.forName("org.wyj.proxy.cg_proxy.TrainStation$$EnhancerByCGLIB$$c8c06447");Class<?> clazz2 = Class.forName("java.lang.Object");Method[] methodArray = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, clazz2.getDeclaredMethods());CGLIB$finalize$1$Method = methodArray[0];CGLIB$finalize$1$Proxy = MethodProxy.create(clazz2, clazz, "()V", "finalize", "CGLIB$finalize$1");CGLIB$equals$2$Method = methodArray[1];CGLIB$equals$2$Proxy = MethodProxy.create(clazz2, clazz, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");CGLIB$toString$3$Method = methodArray[2];CGLIB$toString$3$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");CGLIB$hashCode$4$Method = methodArray[3];CGLIB$hashCode$4$Proxy = MethodProxy.create(clazz2, clazz, "()I", "hashCode", "CGLIB$hashCode$4");CGLIB$clone$5$Method = methodArray[4];CGLIB$clone$5$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");clazz2 = Class.forName("org.wyj.proxy.cg_proxy.TrainStation");CGLIB$sell$0$Method = ReflectUtils.findMethods(new String[]{"sell", "()V"}, clazz2.getDeclaredMethods())[0];CGLIB$sell$0$Proxy = MethodProxy.create(clazz2, clazz, "()V", "sell", "CGLIB$sell$0");}final void CGLIB$sell$0() {super.sell();}// 重写目标类中的方法,在方法中调用用户设置的拦截器public final void sell() {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {Object object = methodInterceptor.intercept(this, CGLIB$sell$0$Method, CGLIB$emptyArgs, CGLIB$sell$0$Proxy);return;}super.sell();}final void CGLIB$finalize$1() throws Throwable {super.finalize();}// 重写Object类中的方法protected final void finalize() throws Throwable {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {Object object = methodInterceptor.intercept(this, CGLIB$finalize$1$Method, CGLIB$emptyArgs, CGLIB$finalize$1$Proxy);return;}super.finalize();}final boolean CGLIB$equals$2(Object object) {return super.equals(object);}public final boolean equals(Object object) {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {Object object2 = methodInterceptor.intercept(this, CGLIB$equals$2$Method, new Object[]{object}, CGLIB$equals$2$Proxy);return object2 == null ? false : (Boolean)object2;}return super.equals(object);}final String CGLIB$toString$3() {return super.toString();}public final String toString() {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {return (String)methodInterceptor.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy);}return super.toString();}final int CGLIB$hashCode$4() {return super.hashCode();}public final int hashCode() {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {Object object = methodInterceptor.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);return object == null ? 0 : ((Number)object).intValue();}return super.hashCode();}final Object CGLIB$clone$5() throws CloneNotSupportedException {return super.clone();}protected final Object clone() throws CloneNotSupportedException {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {return methodInterceptor.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy);}return super.clone();}public static MethodProxy CGLIB$findMethodProxy(Signature signature) {String string = ((Object)signature).toString();switch (string.hashCode()) {case -1574182249: {if (!string.equals("finalize()V")) break;return CGLIB$finalize$1$Proxy;}case -508378822: {if (!string.equals("clone()Ljava/lang/Object;")) break;return CGLIB$clone$5$Proxy;}case 1826985398: {if (!string.equals("equals(Ljava/lang/Object;)Z")) break;return CGLIB$equals$2$Proxy;}case 1913648695: {if (!string.equals("toString()Ljava/lang/String;")) break;return CGLIB$toString$3$Proxy;}case 1978249955: {if (!string.equals("sell()V")) break;return CGLIB$sell$0$Proxy;}case 1984935277: {if (!string.equals("hashCode()I")) break;return CGLIB$hashCode$4$Proxy;}}return null;}public TrainStation$$EnhancerByCGLIB$$c8c06447() {TrainStation$$EnhancerByCGLIB$$c8c06447 trainStation$$EnhancerByCGLIB$$c8c06447 = this;TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(trainStation$$EnhancerByCGLIB$$c8c06447);}public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] callbackArray) {CGLIB$THREAD_CALLBACKS.set(callbackArray);}public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] callbackArray) {CGLIB$STATIC_CALLBACKS = callbackArray;}private static final void CGLIB$BIND_CALLBACKS(Object object) {block2: {Object object2;block3: {TrainStation$$EnhancerByCGLIB$$c8c06447 trainStation$$EnhancerByCGLIB$$c8c06447 = (TrainStation$$EnhancerByCGLIB$$c8c06447)object;if (trainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BOUND) break block2;trainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BOUND = true;object2 = CGLIB$THREAD_CALLBACKS.get();if (object2 != null) break block3;object2 = CGLIB$STATIC_CALLBACKS;if (CGLIB$STATIC_CALLBACKS == null) break block2;}trainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])object2)[0];}}public Object newInstance(Callback[] callbackArray) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$SET_THREAD_CALLBACKS(callbackArray);TrainStation$$EnhancerByCGLIB$$c8c06447 trainStation$$EnhancerByCGLIB$$c8c06447 = new TrainStation$$EnhancerByCGLIB$$c8c06447();TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$SET_THREAD_CALLBACKS(null);return trainStation$$EnhancerByCGLIB$$c8c06447;}public Object newInstance(Callback callback) {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$SET_THREAD_CALLBACKS(new Callback[]{callback});TrainStation$$EnhancerByCGLIB$$c8c06447 trainStation$$EnhancerByCGLIB$$c8c06447 = new TrainStation$$EnhancerByCGLIB$$c8c06447();TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$SET_THREAD_CALLBACKS(null);return trainStation$$EnhancerByCGLIB$$c8c06447;}public Object newInstance(Class[] classArray, Object[] objectArray, Callback[] callbackArray) {TrainStation$$EnhancerByCGLIB$$c8c06447 trainStation$$EnhancerByCGLIB$$c8c06447;TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$SET_THREAD_CALLBACKS(callbackArray);Class[] classArray2 = classArray;switch (classArray.length) {case 0: {trainStation$$EnhancerByCGLIB$$c8c06447 = new TrainStation$$EnhancerByCGLIB$$c8c06447();break;}default: {throw new IllegalArgumentException("Constructor not found");}}TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$SET_THREAD_CALLBACKS(null);return trainStation$$EnhancerByCGLIB$$c8c06447;}public Callback getCallback(int n) {MethodInterceptor methodInterceptor;TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);switch (n) {case 0: {methodInterceptor = this.CGLIB$CALLBACK_0;break;}default: {methodInterceptor = null;}}return methodInterceptor;}public void setCallback(int n, Callback callback) {switch (n) {case 0: {this.CGLIB$CALLBACK_0 = (MethodInterceptor)callback;break;}}}public Callback[] getCallbacks() {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$BIND_CALLBACKS(this);TrainStation$$EnhancerByCGLIB$$c8c06447 trainStation$$EnhancerByCGLIB$$c8c06447 = this;return new Callback[]{this.CGLIB$CALLBACK_0};}public void setCallbacks(Callback[] callbackArray) {Callback[] callbackArray2 = callbackArray;TrainStation$$EnhancerByCGLIB$$c8c06447 trainStation$$EnhancerByCGLIB$$c8c06447 = this;this.CGLIB$CALLBACK_0 = (MethodInterceptor)callbackArray[0];}static {TrainStation$$EnhancerByCGLIB$$c8c06447.CGLIB$STATICHOOK1();}
}
总结:代理类是目标类的子类,会重写它从父类中继承来的方法,包括Object类中的方法。在重写的方法中,调用拦截器来完成任务,拦截器是用户提供的,指定了增强方法和目标对象的执行方式。
代理类失效的情况
基于jdk创建的代理类,代理类只可以代理目标接口中的方法,因为代理类只会实现目标接口中的方法。
基于cglib创建的代理类,代理类只可以代理目标类中可以被继承的方法,因为代理类只会重写可以被继承的方法,如果方法无法被继承,在执行时,会直接调用目标类中的方法,从而导致代理类无法代理。
代理类的执行效率
测试方式:执行100万次,每一次都创建一个代理类,然后调用一次代理类中的方法,方法中什么都不做,这里只关心方法的调用速度
public class JdkProxyClient {public static void main(String[] args) {TrainStation targetObj = new TrainStation();long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {SellTickets proxyObj = (SellTickets) JdkProxyFactory.getTrainStationProxyObj(targetObj);proxyObj.sell();}long executeTime = System.currentTimeMillis() - startTime;System.out.println("执行时间:" + executeTime + "ms");}
}
得出的结论:
| 测试方式 | 基于jdk创建的代理类 | 基于cglib创建的代理类 |
|---|---|---|
| 创建100万个代理类,每个代理类都执行一次 | 112ms | 1130ms |
| 创建1个代理类,每个代理类都执行100万次 | 29ms | 34ms |
可以看出,执行速度差不多,但是jdk创建代理类的速度更快,因为jdk生成的字节码更少
源码解析
jdk创建代理的方式
API总结:
Proxy:public class Proxy implements java.io.Serializable:创建代理类的工具类,提供了静态方法,用于创建动态代理类和动态代理对象,它也是所有动态代理类的父类。
public class Proxy implements java.io.Serializable {private static final long serialVersionUID = -2222568056686623797L;// 构造器的参数,动态生成的代理类,会生成一个构造方法,这里就是构造方法的参数/** parameter types of a proxy class constructor */private static final Class<?>[] constructorParams ={ InvocationHandler.class };// 创建代理类并且获取代理对象的方法,用户需要提供三个参数,用于加载代理类的类加载器、// 代理类需要实现的接口、代理类中需要执行的增强方法。@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/// 在这一步会生成代理类Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 获取代理类中指定的构造方法final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}
}
InvocationHandler:public interface InvocationHandler:调用处理器,在调用处理器中调用增强方法和目标对象。每个代理实例有一个相关联的调用处理器,代理对象内部实际上是通过调用处理器来工作的
public interface InvocationHandler {// 参数proxy是代理对象// 参数method是目标类中的方法的Method对象,调用哪个方法,这儿就是哪个方法的实例// 参数args是method方法的参数public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}
这里对源码的介绍很浅,重点是要理解创建代理类需要提供哪些参数、代理类的执行机制。
相关文章:
java 设计模式之代理模式
简介 代理模式:使用代理类来增强目标类的功能。在代码结构上,代理对象持有目标对象,通过代理对象访问目标对象,这样可以在不改变目标对象的前提下增加额外的功能,如权限校验、缓存等 代理模式内部的角色:…...
外接键盘与笔记本命令键键位不同解决方案(MacOS)
文章目录 修改键位第一步:打开设置第二步:进入键盘快捷键第三步:修改修饰键设置第四步:调整键位第五步:保存设置tips ikbc c87键盘win键盘没反应的解决亲测的方法这是百度的答案标题常规组合键尝试:型号差…...
python爬虫复习
requests模块 爬虫的分类 通用爬虫:将一整张页面进行数据采集聚焦爬虫:可以将页面中局部或指定的数据进行采集 聚焦爬虫是需要建立在通用的基础上来实现 功能爬虫:基于selenium实现的浏览器自动化的操作分布式爬虫:使用分布式机群…...
kotlin知识体系(五) :Android 协程全解析,从作用域到异常处理的全面指南
1. 什么是协程 协程(Coroutine)是轻量级的线程,支持挂起和恢复,从而避免阻塞线程。 2. 协程的优势 协程通过结构化并发和简洁的语法,显著提升了异步编程的效率与代码质量。 2.1 资源占用低(一个线程可运行多个协程)…...
vscode stm32 variable uint32_t is not a type name 问题修复
问题 在使用vscodekeil开发stm32程序时,发现有时候vscode的自动补全功能失效,且problem窗口一直在报错。variable “uint32_t” is not a type name uint32_t 定义位置 uint32_t 实际是在D:/Keil_v5/ARM/ARMCC/include/stdint.h中定义的。将D:/Keil_v5…...
Formality:Bug记录
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 本文记录博主在使用Synopsys的形式验证工具Formality中遇到的一个Bug。 Bug复现 情况一 // 例1 module dff (input clk, input d_in, output d_out …...
在ubuntu20.04+系统部署VUE及Django项目的过程记录——以腾讯云为例
目录 1. 需求2. 项目准备3. VUE CLI项目部署3.1 部署前的准备3.1.1 后端通信路由修改3.1.2 导航修改 3.2 构建项目3.3 配置nginx代理 4. 后端配置4.1 其他依赖项4.2 单次执行测试4.3 创建Systemd 服务文件4.4 配置 Nginx 作为反向代理 5. 其他注意事项 1. 需求 近期做一些简单…...
回归,git 分支开发操作命令
核心分支说明 主分支(master/production)存放随时可部署到生产环境的稳定代码,仅接受通过测试的合并请求。 开发分支(develop)集成所有功能开发的稳定版本,日常开发的基础分支,从该分支创建特性…...
【java+Mysql】学生信息管理系统
学生信息管理系统是一种用于管理学生信息的软件系统,旨在提高学校管理效率和服务质量。本课程设计报告旨在介绍设计和实现学生信息管理系统的过程。报告首先分析了系统的需求,包括学生基本信息管理、成绩管理等功能。接着介绍了系统的设计方案࿰…...
小白从0学习网站搭建的关键事项和避坑指南(2)
以下是针对小白从零学习网站搭建的 进阶注意事项和避坑指南(第二期),覆盖开发中的高阶技巧、常见陷阱及解决方案,帮助你在实战中提升效率和质量: 一、进阶技术选型避坑 1. 前端框架选择 误区:盲目追求最新…...
Windows 10 上安装 Spring Boot CLI详细步骤
在 Windows 10 上安装 Spring Boot CLI 可以通过以下几种方式完成。以下是详细的步骤说明: 1. 手动安装(推荐) 步骤 1:下载 Spring Boot CLI 访问 Spring Boot CLI 官方发布页面。下载最新版本的 .zip 文件(例如 sp…...
spring boot -- 配置文件application.properties 换成 application.yml
在Spring Boot项目中,application.properties和application.yml是两种常用的配置文件格式,它们各自具有不同的特点和适用场景2。以下是它们之间的主要差异2: 性能差异 4: 加载机制 2: application.properties文件会被加载到内存中,并且只加载一次,之后直接从内存中读取…...
Spring Boot实战:基于策略模式+代理模式手写幂等性注解组件
一、为什么需要幂等性? 核心定义:在分布式系统中,一个操作无论执行一次还是多次,最终结果都保持一致。 典型场景: 用户重复点击提交按钮网络抖动导致的请求重试消息队列的重复消费支付系统的回调通知 不处理幂等的风…...
【Rust 精进之路之第14篇-结构体 Struct】定义、实例化与方法:封装数据与行为
系列: Rust 精进之路:构建可靠、高效软件的底层逻辑 作者: 码觉客 发布日期: 2025-04-20 引言:超越元组,给数据赋予意义 在之前的学习中,我们了解了 Rust 的基本数据类型(标量)以及两种基础的复合类型:元组 (Tuple) 和数组 (Array)。元组允许我们将不同类型的值组合…...
postgres 数据库信息解读 与 sqlshell常用指令介绍
数据库信息: sqlshell Server [localhost]: 192.168.30.101 Database [postgres]: Port [5432]: 5432 Username [postgres]: 用户 postgres 的口令: psql (15.12, 服务器 16.8 (Debian 16.8-1.pgdg120+1)) 警告:psql 主版本15,服务器主版本为16.一些psql功能可能无法正常使…...
论文阅读:2024 arxiv DeepInception: Hypnotize Large Language Model to Be Jailbreaker
总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 DeepInception: Hypnotize Large Language Model to Be Jailbreaker DeepInception:催眠大型语言模型,助你成为越狱者 https://arxiv.org/pdf/2311.0…...
vue2技术练习-开发了一个宠物相关的前端静态商城网站-宠物商城网站
为了尽快学习掌握相关的前端技术,最近又实用 vue2做了一个宠物行业的前端静态网站商城。还是先给大家看一下相关的网站效果: 所以大家如果想快速的学习或者掌握一门编程语言,最好的方案就是通过学习了基础编程知识后,就开始利用…...
嵌入式学习——远程终端登录和桌面访问
目录 通过桥接模式连接虚拟机和Windows系统 1、桥接模式 2、虚拟机和Windows连接(1) 3、虚拟机和Windows连接(2) 在Linux虚拟机中创建新用户 Windows系统环境下对Linux系统虚拟机操作 远程登录虚拟机(1ÿ…...
wpf stylet框架 关于View与viewmodel自动关联绑定的问题
1.1 命名规则 Aview 对应 AVIewModel, 文件夹 views 和 viewmodels 1.2 需要注册服务 //RootViewModel是主窗口 public class Bootstrapper : Bootstrapper<RootViewModel>{/// <summary>/// 配置IoC容器。为数据共享创建服务/// </summary…...
如何新建一个空分支(不继承 master 或任何提交)
一、需求分析: 在 Git 中,我们通常通过 git branch 来新建分支,这些分支默认都会继承当前所在分支的提交记录。但有时候我们希望新建一个“完全干净”的分支 —— 没有任何提交,不继承 master 或任何已有内容,这该怎么…...
HarmonyOS-ArkUI-动画分类简介
本文的目的是,了解一下HarmonyOS动画体系中的分类。有个大致的了解即可。 动效与动画简介 动画,是客户端提升界面交互用户体验的一个重要的方式。可以使应用程序更加生动灵越,提高用户体验。 HarmonyOS对于界面的交互方面,围绕回归本源的设计理念,打造自然,流畅品质一提…...
Qt编写推流程序/支持webrtc265/从此不用再转码/打开新世界的大门
一、前言 在推流领域,尤其是监控行业,现在主流设备基本上都是265格式的视频流,想要在网页上直接显示监控流,之前的方案是,要么转成hls,要么魔改支持265格式的flv,要么265转成264,如…...
[第十六届蓝桥杯 JavaB 组] 真题 + 经验分享
A:逃离高塔(AC) 这题就是简单的签到题,按照题意枚举即可。需要注意的是不要忘记用long,用int的话会爆。 📖 代码示例: import java.io.*; import java.util.*; public class Main {public static PrintWriter pr ne…...
深⼊理解 JVM 执⾏引擎
深⼊理解 JVM 执⾏引擎 其中前端编译是在 JVM 虚拟机之外执⾏,所以与 JVM 虚拟机没有太⼤的关系。任何编程语⾔,只要能够编译出 满⾜ JVM 规范的 Class ⽂件,就可以提交到 JVM 虚拟机执⾏。⾄于编译的过程,如果你不是想要专⻔去研…...
iwebsec靶场 文件包含关卡通关笔记11-ssh日志文件包含
目录 日志包含 1.构造恶意ssh登录命令 2.配置ssh日志开启 (1)配置sshd (2)配置rsyslog (3)重启服务 3.写入webshell木马 4.获取php信息渗透 5.蚁剑连接 日志包含 1.构造恶意ssh登录命令 ssh服务…...
kafka菜鸟教程
一、kafka原理 1、kafka是一个高性能的消息队列系统,能够处理大规模的数据流,并提供低延迟的数据传输,它能够以每秒数十万条消息的速度进行读写操作。 二、kafka优点 1、服务解耦 (1)提高系统的可维护性 通过服务…...
应用镜像是什么?轻量应用服务器的镜像大全
应用镜像是轻量应用服务器专属的,镜像就是轻量应用服务器的装机盘,应用镜像在原有的纯净版操作系统上集成了应用程序,例如WordPress应用镜像、宝塔面板应用镜像、WooCommerce等应用,阿里云服务器网aliyunfuwuqi.com整理什么是轻量…...
深入理解分布式缓存 以及Redis 实现缓存更新通知方案
一、分布式缓存简介 1. 什么是分布式缓存 分布式缓存:指将应用系统和缓存组件进行分离的缓存机制,这样多个应用系统就可以共享一套缓存数据了,它的特点是共享缓存服务和可集群部署,为缓存系统提供了高可用的运行环境,…...
Spring Boot 中的自动配置原理
2025/4/6 向全栈工程师迈进! 一、自动配置 所谓的自动配置原理就是遵循约定大约配置的原则,在boot工程程序启动后,起步依赖中的一些bean对象会自动的注入到IOC容器中。 在讲解Spring Boot 中bean对象的管理的时候,我们注入bean对…...
软考高级-系统架构设计师 论文范文参考(一)
文章目录 论SOA技术的应用论SOA在企业信息化中的应用论UP(统一过程方法)的应用论分布式数据库的设计与实现论改进Web服务器性能的有关技术论基于UML的需求分析论基于构件的软件开发论基于构件的软件开发(二) 论SOA技术的应用 摘要: 本人于…...
