代理模式详解

1.基本介绍

  1. 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能;
  2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象;
  3. 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK代理、接口代理) 和 Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。

2.示意图

在这里插入图片描述

3.静态代理

3.1 基本介绍

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类

3.2 应用实例

3.2.1 基本要求

1) 定义一个接口:ITeacherDao 
2) 目标对象 TeacherDAO 实现接口 ITeacherDAO 
3) 使用静态代理方式,就需要在代理对象 TeacherDAOProxy 中也实现 ITeacherDAO 
4) 调用的时候通过调用代理对象的方法来调用目标对象.
5) 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

3.2.2 类图

在这里插入图片描述

3.2.3 代码实现

//接口
public interface ITeacherDao {public void teach();//授课的方法
}public class TeacherDao implements ITeacherDao {@Overridepublic void teach() {System.out.println(" 老师授课中... ");}
}//代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao {ITeacherDao target;//目标对象,通过接口来聚合//构造器public TeacherDaoProxy(ITeacherDao target) {this.target = target;}@Overridepublic void teach() {System.out.println("静态代理开始,完成某些操作...");//方法target.teach();System.out.println("静态代理提交...");//方法}
}public class Client {public static void main(String[] args) {//创建目标对象(被代理对象)TeacherDao teacherDao = new TeacherDao();//创建代理对象, 同时将被代理对象传递给代理对象TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);//通过代理对象,调用到被代理对象的方法//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法teacherDaoProxy.teach();}
}

3.3 优缺点

  1. 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
  2. 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
  3. 一旦接口增加方法,目标对象与代理对象都要维护

4.动态代理

4.1 基本介绍

  1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
  2. 代理对象的生成,是利用 JDK的API,动态的在内存中构建代理对象
  3. 动态代理也叫做:JDK代理、接口代理

4.2 应用实例

4.2.1 类图

在这里插入图片描述

4.2.2 代码实现

//接口
public interface ITeacherDao {public void teach();//授课的方法public void sayHello(String name);
}// 目标类
public class TeacherDao implements ITeacherDao {@Overridepublic void teach() {System.out.println(" 老师授课中... ");}@Overridepublic void sayHello(String name) {System.out.println("Hello," + name);}
}// 代理工厂,负责生成代理对象
public class ProxyFactory {//维护一个目标对象(被代理对象),ObjectObject target;//构造器,对target进行初始化public ProxyFactory(Object target) {this.target = target;}//给目标对象(被代理对象)生成一个代理对象public Object getProxyInstance() {//说明/**  public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入*/return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK代理开始~~");//反射机制调用目标对象的方法Object returnVal = method.invoke(target, args);System.out.println("JDK代理提交");return returnVal;}});}
}public class Client {public static void main(String[] args) {//创建目标对象ITeacherDao target = new TeacherDao();//给目标对象,创建代理对象, 可以转成 ITeacherDaoITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象System.out.println("proxyInstance=" + proxyInstance.getClass());//通过代理对象,调用目标对象的方法proxyInstance.teach();proxyInstance.sayHello("Jack");}
}

4.3 手动模拟实现底层原理

先定义一个Foo接口,里面有一个foo()方法,再定义一个Target类来实现这个接口

第一版:定义一个类也同样地实现一下Foo接口,然后在foo()方法中编写增强代码,接着再new一个Target对象,调用它的foo()方法,代码如下所示:

public class Main {public static void main(String[] args) {Foo proxy=new $Proxy0();proxy.foo();// 结果:// 代码增强// target foo() }
}interface Foo{void foo();
}class Target implements Foo{@Overridepublic void foo() {System.out.println(" target foo() ");}
}class $Proxy0 implements Foo{@Overridepublic void foo() {System.out.println("代码增强");new Target().foo();}
}

第二版:上面的代码把功能增强的代码和调用目标的代码都固定在了代理类的内部,不太灵活。因此可以通过定义一个InvocationHandler接口的方式来将这部分代码解耦出来,代码如下:

public class Main {public static void main(String[] args) {Foo proxy=new $Proxy0(new InvocationHandler() {@Overridepublic void invoke() {System.out.println("代码增强");new Target().foo();}});proxy.foo();// 结果:// 代码增强// target foo() }
}interface Foo{void foo();
}class Target implements Foo{@Overridepublic void foo() {System.out.println(" target foo() ");}
}class $Proxy0 implements Foo{private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {h.invoke();}
}interface InvocationHandler{void invoke();
}

第三版:第2个版本的代码虽然将功能增强的代码和调用目标的代码通过接口的方式独立出来了,但还是有问题,如果此时接口中新增了一个方法bar(),Target类和$Proxy0类中都要实现bar()方法,那么调用proxy的foo()和bar()方法都将间接调用目标对象的foo()方法,因为在InvocationHandler的invoke()方法中调用的是target.foo()方法,代码如下:

public interface InvocationHandler {void invoke();
}public interface Foo {void foo();void bar();
}@Slf4j
public final class Target implements Foo {public void foo() {System.out.println("target foo");}@Overridepublic void bar() {log.debug("target bar");}
}public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {h.invoke();}@Overridepublic void bar() {h.invoke();}
}public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke() {// 1. 功能增强System.out.println("before...");// 2. 调用目标new Target().foo();}});proxy.foo();proxy.bar();}
}

改进方法是,代理类中调用方法的时候,通过反射把接口中对应的方法Method对象作为参数传给InvocationHandler,代码如下:

public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1. 功能增强System.out.println("代码增强");// 2. 调用目标method.invoke(new Target(), args);}});proxy.foo();proxy.bar();// 结果:// 代码增强// target foo() // 代码增强// target bar() }
}interface Foo {void foo();void bar();
}class Target implements Foo {@Overridepublic void foo() {System.out.println(" target foo() ");}@Overridepublic void bar() {System.out.println(" target bar() ");}
}class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {Method foo = Foo.class.getDeclaredMethod("foo");h.invoke(foo, new Object[0]);} catch (Throwable e) {e.printStackTrace();}}@Overridepublic void bar() {try {Method bar = Foo.class.getDeclaredMethod("bar");h.invoke(bar, new Object[0]);} catch (Throwable e) {e.printStackTrace();}}
}interface InvocationHandler {void invoke(Method method, Object[] args) throws Throwable;
}

第四版:第3个版本的代码其实已经离jdk动态代理生成的代码很相近了,为了更好地学习底层,更近一步,修改Foo接口的中bar()方法,使其具有int类型的返回值,因此InvocationHandlerinvoke()方法也得有返回值,同时将代理对象本身作为第一个参数,具体代码如下:

public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1. 功能增强System.out.println("代码增强");// 2. 调用目标return method.invoke(new Target(), args);}});proxy.foo();System.out.println(proxy.bar());}
}interface Foo {void foo();int bar();
}class Target implements Foo {@Overridepublic void foo() {System.out.println(" target foo() ");}@Overridepublic int bar() {System.out.println(" target bar() ");return 100;}
}class $Proxy0 implements Foo {static Method foo;static Method bar;static {try {foo = Foo.class.getDeclaredMethod("foo");bar = Foo.class.getDeclaredMethod("bar");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {h.invoke(this, foo, new Object[0]);} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {// 如果抛出RuntimeExcepton,Error和声明的异常以及其子类外的其他异常,// 都会被统一转换成 UndeclaredThrowableException 进行抛出,// 这也是实现“子类不能抛出比父类更广泛异常"的规则throw new UndeclaredThrowableException(e);}}@Overridepublic int bar() {try {return (int) h.invoke(this, bar, new Object[0]);} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}interface InvocationHandler {Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

到这里跟jdk的动态代理只有些微差距了,jdk的动态代码会让代理类再继承一个Proxy类,里面定义了一个InvocationHandler接口的对象,代理类中会通过super(h)调用父类Proxy的构造。

5.cglib代理

5.1 基本介绍

  1. 静态代理和 JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib 代理

  2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。

  3. Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截

  4. 在AOP 编程中如何选择代理模式:

    ①目标对象需要实现接口,用 JDK代理

    ②目标对象不需要实现接口,用 Cglib 代理

  1. Cglib 包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

5.2 注意事项

①在内存中动态构建子类,注意代理的类不能为 final,否则报错 java.lang.IllegalArgumentException:

②目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

5.3 应用实例

5.3.1 类图

在这里插入图片描述

5.3.2 代码实现

public class Main {public static void main(String[] args) {//创建目标对象TeacherDao target = new TeacherDao();//获取到代理对象,并且将目标对象传递给代理对象TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();//执行代理对象的方法,触发intercept 方法,从而实现 对目标对象的调用proxyInstance.teach();// 结果://Cglib代理模式 ~~ 开始// 老师授课中, 使用cglib代理,不需要实现接口... //Cglib代理模式 ~~ 提交}
}class TeacherDao {public void teach() {System.out.println(" 老师授课中, 使用cglib代理,不需要实现接口... ");}
}class ProxyFactory implements MethodInterceptor {//维护一个目标对象(被代理对象),ObjectObject target;//构造器,对target进行初始化public ProxyFactory(Object target) {this.target = target;}//返回一个代理对象:  是 target 对象的代理对象public Object getProxyInstance() {//1. 创建一个工具类Enhancer enhancer = new Enhancer();//2. 设置父类enhancer.setSuperclass(target.getClass());//3. 设置回调函数enhancer.setCallback(this);//4. 创建子类对象,即代理对象return enhancer.create();}//重写  intercept 方法,会调用目标对象的方法@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("Cglib代理模式 ~~ 开始");// 采用反射调用的方式Object returnVal = method.invoke(target, objects);// 内部没有反射调用,但需要结合目标对象使用// Object returnVal = methodProxy.invoke(target, objects);// 内部没有反射调用,但需要结合代理对象使用// Object returnVal = methodProxy.invokeSuper(o, args);System.out.println("Cglib代理模式 ~~ 提交");return returnVal;}
}

5.4 手动模拟实现底层原理

采用反射调用的方式

public class Main {public static void main(String[] args) {Target target = new Target();Proxy proxy = new Proxy();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before");// 反射调用return method.invoke(target, args);}});proxy.save();proxy.save(1);proxy.save(2L);// before// save()// before// save(int)// before// save(long)}
}// 目标类
class Target {public void save() {System.out.println("save()");}public void save(int i) {System.out.println("save(int)");}public void save(long i) {System.out.println("save(long)");}
}class Proxy extends Target {private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long i) {try {methodInterceptor.intercept(this, save2, new Object[]{i}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}

在 CGLib 动态代理中,可以使用 intercept() 方法中 MethodProxy 类型的参数实现不经过反射来调用方法。接收的 MethodProxy 类型的参数可以像 Method 类型的参数一样,在静态代码块中被实例化。可以通过静态方法 MethodProxy.create() 来创建 MethodProxy 对象。内部没有反射调用,但需要结合目标对象或者代理对象结合使用:

public class Main {public static void main(String[] args) {Target target = new Target();Proxy proxy = new Proxy();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before");// 内部没有反射调用,但需要结合目标对象使用
//                return methodProxy.invoke(target, args);// 内部没有反射调用,但需要结合代理对象使用return methodProxy.invokeSuper(o, args);}});proxy.save();proxy.save(1);proxy.save(2L);//before//save()//before//save(int)//before//save(long)}
}// 目标类
class Target {public void save() {System.out.println("save()");}public void save(int i) {System.out.println("save(int)");}public void save(long i) {System.out.println("save(long)");}
}class Proxy extends Target {private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static MethodProxy save0Proxy;static MethodProxy save1Proxy;static MethodProxy save2Proxy;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}// >>>>>>>>>>>>>>>>>>>>>>>> 带原始功能的方法public void saveSuper() {super.save();}public void saveSuper(int i) {super.save(i);}public void saveSuper(long i) {super.save(i);}// >>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long i) {try {methodInterceptor.intercept(this, save2, new Object[]{i}, save2Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}

5.5 MethodProxy 原理

调用 methodProxy.invoke() 方法时,会额外使用一个代理类,该代理类配合目标对象使用。调用 methodProxy.invokeSuper() 方法时,也会额外使用一个代理类,该代理类配合代理对象使用。当调用 MethodProxy 对象的 invoke() 方法或 invokeSuper() 方法时,就会生成这两个代理类,它们都继承至 FastClass。FastClass 是一个抽象类,其内部有多个抽象方法:

public abstract class FastClass {public abstract int getIndex(String var1, Class[] var2);public abstract int getIndex(Class[] var1);public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;public abstract Object newInstance(int var1, Object[] var2) throws InvocationTargetException;public abstract int getIndex(Signature signature);public abstract int getMaxIndex();
}

重点讲解 invoke() 方法与 getIndex(Signature signature) 方法。

模拟生成的与目标类相关的代理类:

public class TargetFastClass {static Signature s0 = new Signature("save", "()V");static Signature s1 = new Signature("save", "(I)V");static Signature s2 = new Signature("save", "(J)V");/*** <p>获取目标方法的编号</p>* <p>* Target 目标类中的方法:* save()             0* save(int)          1* save(long)         2* </p>** @param signature 包含方法名称、参数返回值* @return 方法编号*/public int getIndex(Signature signature) {if (s0.equals(signature)) {return 0;}if (s1.equals(signature)) {return 1;}if (s2.equals(signature)) {return 2;}return -1;}/*** 根据 getIndex() 方法返回的方法编号正常调用目标对象方法** @param index       方法编号* @param target       目标对象* @param args 调用目标对象方法需要的参数* @return 方法返回结果*/public Object invoke(int index, Object target, Object[] args) {if (index == 0) {((Target) target).save();return null;}if (index == 1) {((Target) target).save((int) args[0]);return null;}if (index == 2) {((Target) target).save((long) args[0]);return null;}throw new RuntimeException("无此方法");}public static void main(String[] args) {TargetFastClass fastClass = new TargetFastClass();int index = fastClass.getIndex(new Signature("save", "()V"));fastClass.invoke(index, new Target(), new Object[0]);index = fastClass.getIndex(new Signature("save", "(J)V"));fastClass.invoke(index, new Target(), new Object[]{2L});// 结果:// save()// save(long)}
}

模拟生成的与代理类相关的代理类

public class ProxyFastClass {static Signature s0 = new Signature("saveSuper", "()V");static Signature s1 = new Signature("saveSuper", "(I)V");static Signature s2 = new Signature("saveSuper", "(J)V");/*** <p>获取代理方法的编号</p>* <p>* Proxy 代理类中的方法:* saveSuper()             0* saveSuper(int)          1* saveSuper(long)         2* </p>** @param signature 包含方法名称、参数返回值* @return 方法编号*/public int getIndex(Signature signature) {if (s0.equals(signature)) {return 0;}if (s1.equals(signature)) {return 1;}if (s2.equals(signature)) {return 2;}return -1;}/*** 根据 getIndex() 方法返回的方法编号正常调用代理对象中带原始功能的方法** @param index 方法编号* @param proxy 代理对象* @param args  调用方法需要的参数* @return 方法返回结果*/public Object invoke(int index, Object proxy, Object[] args) {if (index == 0) {((Proxy) proxy).saveSuper();return null;}if (index == 1) {((Proxy) proxy).saveSuper((int) args[0]);return null;}if (index == 2) {((Proxy) proxy).saveSuper((long) args[0]);return null;}throw new RuntimeException("无此方法");}public static void main(String[] args) {ProxyFastClass fastClass = new ProxyFastClass();int index = fastClass.getIndex(new Signature("saveSuper", "()V"));fastClass.invoke(index, new Proxy(), new Object[0]);// 结果:// save()}
}

与 JDK 中优化反射调用方法的对比

在 JDK 中需要反射调用 16 次方法后才会生成优化反射调用的代理类,而在 CGLib 中,当调用 MethodProxy.create() 方法时就会生成由于优化反射调用的代理类;
在 JDK 中一个方法的反射调用优化就要生成一个代理类,而在 CGLib 中,一个代理类生成两个 FastClass 代理类。

6.代理模式的常见变体

  1. 防火墙代理: 内网通过代理穿透防火墙,实现对公网的访问。
  2. 缓存代理: 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据
    库取,然后缓存。
  3. 远程代理 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
  4. 同步代理:主要使用在多线程编程中,完成多线程间同步工作
    同步代理:主要使用在多线程编程中,完成多线程间同步工作

).saveSuper((int) args[0]);
return null;
}
if (index == 2) {
((Proxy) proxy).saveSuper((long) args[0]);
return null;
}
throw new RuntimeException(“无此方法”);
}

public static void main(String[] args) {ProxyFastClass fastClass = new ProxyFastClass();int index = fastClass.getIndex(new Signature("saveSuper", "()V"));fastClass.invoke(index, new Proxy(), new Object[0]);// 结果:// save()
}

}


### 与 JDK 中优化反射调用方法的对比```tex
在 JDK 中需要反射调用 16 次方法后才会生成优化反射调用的代理类,而在 CGLib 中,当调用 MethodProxy.create() 方法时就会生成由于优化反射调用的代理类;
在 JDK 中一个方法的反射调用优化就要生成一个代理类,而在 CGLib 中,一个代理类生成两个 FastClass 代理类。

6.代理模式的常见变体

  1. 防火墙代理: 内网通过代理穿透防火墙,实现对公网的访问。
  2. 缓存代理: 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据
    库取,然后缓存。
  3. 远程代理 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
  4. 同步代理:主要使用在多线程编程中,完成多线程间同步工作
    同步代理:主要使用在多线程编程中,完成多线程间同步工作

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记录:uniapp直播的弹幕的样式修改与发送弹幕会自动滚动到底部两个技巧

1、在直播页面的弹幕评论中&#xff0c;我们希望的样式是&#xff1a; 观众名字&#xff1a;评论 而且颜色有所区分&#xff0c;并在同一行显示 2、我们希望在发弹幕的时候可以回自动滚动到自己发的内容那里 一&#xff1a;弹幕样式修改 因为是小白&#xff0c;前端对于样式这…

苹果手机照片被删除?如何通过不同的方法来恢复照片

手机已经成为我们生活中不可或缺的一部分&#xff0c;它不仅仅是通讯工具&#xff0c;更是我们记录生活点滴的重要工具之一。然而&#xff0c;正如其他任何设备一样&#xff0c;iPhone上存储的照片有时也会不小心被删除或丢失。 别担心&#xff0c;即使你误删了重要的照片&…

重头开始嵌入式第三十四天(数据库二)

sqlite3的一些补充 目录 sqlite3的一些补充 1.事物 2.连接&#xff0c;联合 3.触发器 4.子查询 1.事物 数据库事务是数据库管理系统执行过程中的一个逻辑单位&#xff0c;它由一系列对数据库的操作组成。 一、事务的特性 1. 原子性&#xff08;Atomicity&#xff09…

Linux:目录及文件管理

目录及文件管理 cd的命令使用 . 当前目录 .. 父目录&#xff08;上一层&#xff09; ~ 表示家目录 家目录&#xff1a;专门存放用户个性化信息的目录 ~user&#xff1a;用户user的家目录 /root: 是Linux管理员的家目录 /home: 存放所有普通用户的家目录]# cd ~root #去…

大模型LLM算法工程师技术面试指南

大模型LLM算法工程师技术面试指南 AI大模型全套学习资料 “最先掌握AI的人&#xff0c;将会比较晚掌握AI的人有竞争优势”。 这句话&#xff0c;放在计算机、互联网、移动互联网的开局时期&#xff0c;都是一样的道理。 我在一线互联网企业工作十余年里&#xff0c;指导过不少…

Java异常类

目录 Java异常类 Java中的异常体系 抛出异常 处理异常 处理异常的两种方式 try...catch和throws的区别 finally关键字 抛出异常注意事项 自定义异常类 Java异常类 Java中的异常体系 在Java中&#xff0c;异常类的父类为Throwable类&#xff0c;在Throwable下&#x…

记一次高版本view-design的组件迁移到自身项目的低版本

背景 npm i -S view-design当前老项目使用view-design这个组件库&#xff0c;但是当我们去官网查看该组件库最新版本&#xff0c;竟然发现没有博主想用的image/ImagePreivew这两个基础组件 说实话&#xff0c;有点离谱了哈&#xff01;&#xff01; 自己造轮子&#xff1f; …

数据结构基本知识

一、什么是数据结构 1.1、组织存储数据 ---------》内存&#xff08;存储&#xff09; 1.2、研究目的 如何存储数据&#xff08;变量&#xff0c;数组....)程序数据结构算法 1.3、常见保存数据的方法 数组&#xff1a;保存自己的数据指针&#xff1a;是间接访问已经存在的…

分库分表核心理念

文章目录 分库&#xff0c;分表&#xff0c;分库分表什么时候分库&#xff1f;什么时候分表&#xff1f;什么时候既分库又分表&#xff1f;横向拆分 & 纵向拆分 分表算法Range 范围Hash 取模一致性 Hash斐波那契散列 严格雪崩标准&#xff08;SAC&#xff09;订单分库分表实…

【880高数】高等数学一刷错题整理

第一章 函数、极限、连续 2024.8.11日 1. 2. 3. 4. 5. 2024.8.12日 1. 2. 3. 4. 5. 6. 7. 8. 2024.8.13日 1. 2. 3. 4. 2024.8.14日 1. 2. 3. 4. 5. 第二章 一元函数微分学及其应用 2024.8.15日 1. 2. 3. 4. 5. 6. 2024.8.16日 1. 2. 3. 4. 5. 2024.8.17日 1. 2. 3. 4…

个人简历 (自己设计的)

欢迎大家来观看。 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" co…

相亲交友系统背后的科技力量:智能匹配的秘密

随着互联网技术的发展&#xff0c;相亲交友系统已经成为许多人寻找另一半的重要工具。这些相亲交友系统不仅仅是一个简单的社交平台&#xff0c;它们背后隐藏着强大的科技力量&#xff0c;尤其是智能匹配技术&#xff0c;使得用户能够更加高效地找到适合自己的伴侣。 相亲交友…

信息学奥赛初赛天天练-87-NOIP2014普及组-完善程序-矩阵、子矩阵、最大子矩阵和、前缀和、打擂台求最大值

1 完善程序 最大子矩阵和 给出 m行 n列的整数矩阵&#xff0c;求最大的子矩阵和(子矩阵不能为空)。 输入第一行包含两个整数 m和 n&#xff0c;即矩阵的行数和列数。之后 m行&#xff0c;每行 n个整数&#xff0c;描述整个矩阵。程序最终输出最大的子矩阵和。 &#xff08;最…

C语言俄罗斯方块(VS2022版)

C语言俄罗斯方块 演示视频一、前置知识1.Win32 API 的使用2.宽字符的使用 二、封装核心数据与框架介绍三、核心操作介绍旋转操作检测操作水平检测竖直检测代码化简 四、源码展示在 tetris.h 中&#xff1a;在 tetris.c 中&#xff1a;在 test.c 中&#xff1a; 以下代码环境为 …

码上进阶_刷题模块测试_用例设计

码上进阶_刷题模块测试_用例设计 系统概述&#xff1a; 码上进阶是为程序员专门打造的交流平台&#xff0c;采用主流的微服务框架和C端技术栈作为技术基础。在这个平台上&#xff0c;程序员 可以通过刷题、练习和模拟面试来提升自己的面试能力。 功能测试&#xff1a; 登录…

SpringBoot OAuth2自定义登陆/授权页

背景 5 月份的时候&#xff0c;我实践并整理了一篇博客&#xff1a;SpringBoot搭建OAuth2&#xff0c;该博客完成之后&#xff0c;很长一段时间里我都有种意犹未尽的感觉。诚然&#xff0c;我把OAuth2搭起来了&#xff0c;各种场景的用例也跑通了&#xff0c;甚至源码也看了&am…

99.WEB渗透测试-信息收集-网络空间搜索引擎shodan(1)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;98.WEB渗透测试-信息收集-Google语法&#xff08;12&#xff09; 信息收集方向-网络空间…

【IDEA配置一个maven项目(详细操作流程)】

目录 一、安装Maven 1、官网下载maven链接地址&#xff1a;Maven – Download Apache Maven 2、下载完成后&#xff0c;解压到某一路径下。E:\JavaTools\apache-maven-3.9.8为例&#xff0c;实际配置环境变量时以自己安装的路径为准。 二、配置环境变量 1、右键此电脑–&g…

springboot、flowable 生成图片发布到Docker乱码问题

flowable自带的方法生成图片时&#xff0c;如设置字体为宋体&#xff0c;则本地测试没有问题&#xff0c;因为windows自带宋体字体库&#xff0c;但是如果发布到Docker&#xff0c;则会出现乱码问题&#xff0c;因为大部分Docker并不包含宋体字体库&#xff1b; 通过Java代码&a…

基于springboot+vue实现的在线商城系统

系统主要功能&#xff1a; &#xff08;1&#xff09;商品管理模块&#xff1a;实现了商品的基本信息录入、图片上传、状态管理等相关功能。 &#xff08;2&#xff09;商品分类模块&#xff1a;实现了分类的增删改查、分类层级管理、商品分类的关联等功能。 &#xff08;3&…