java代理模式(动态代理、静态代理、需要实现类的JDK代理、不需要实现类的JDK动态代理、CGLIB代理)

静态代理简单使用

静态代理是代理模式的一种实现方式,它在编译时就已经确定了被代理对象和代理对象的关系。在静态代理中,需要手动创建一个代理类,该代理类与被代理对象实现相同的接口或继承相同的父类,并在代理类的方法中调用被代理对象的方法。

静态代理的例子:

package com.yimeng.proxy;import com.yimeng.service.BuyPhoneService;/*** @Author yimeng* @Date 2024/9/26 23:22* @PackageName:com.yimeng.proxy* @ClassName: BuyPhoneProxy* @Description: TODO* @Version 1.0*/
public class BuyPhoneProxy implements BuyPhoneService {private final BuyPhoneService buyPhoneService;public BuyPhoneProxy(BuyPhoneService buyPhoneService) {this.buyPhoneService = buyPhoneService;}@Overridepublic void buyPhone() {System.out.println("内部优惠打八折");buyPhoneService.buyPhone();System.out.println("送手机大礼包!!!");}
}
package com.yimeng.service.impl;import com.yimeng.service.BuyPhoneService;/*** @Author yimeng* @Date 2024/9/26 23:21* @PackageName:com.yimeng.service.impl* @ClassName: BuyPhoneServiceImpl* @Description: TODO* @Version 1.0*/
public class BuyPhoneServiceImpl implements BuyPhoneService {@Overridepublic void buyPhone() {System.out.println("买手机");}
}
package com.yimeng.service;/*** @Author yimeng* @Date 2024/9/26 23:21* @PackageName:com.yimeng.service* @ClassName: BuyPhoneService* @Description: TODO* @Version 1.0*/
public interface BuyPhoneService {void buyPhone();
}
package com.yimeng;import com.yimeng.proxy.BuyPhoneProxy;
import com.yimeng.service.BuyPhoneService;
import com.yimeng.service.impl.BuyPhoneServiceImpl;/*** @Author yimeng* @Date 2024/9/26 23:22* @PackageName:com.yimeng* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {BuyPhoneService buyPhoneService = new BuyPhoneServiceImpl();System.out.println("使用代理前");System.out.println("----------------------");buyPhoneService.buyPhone();System.out.println("----------------------");System.out.println("使用代理后");System.out.println("----------------------");BuyPhoneProxy buyPhoneProxy = new BuyPhoneProxy(buyPhoneService);buyPhoneProxy.buyPhone();}
}

结果:

在这里插入图片描述

在编译的时候,就知道要代理的对象是谁了。

在这里插入图片描述

静态代理最大的缺点就是:代码是写死的。如果要给很多类都要加上执行前的输出和执行后的输出,那么就给每一个需要增强的类,创建一个代理类了,这样的话会导致代码冗余,增加维护成本,如果要修改,那么就要把所有的代理类都修改。但是如果使用动态代理,那么就灵活了,只要写一个代理类就行了,这代理类原来执行什么方法,通过反射或者FastClass机制就可以知道了。这种情况下,如果要修改增强的内容,只要改这一个代理类就行了。

动态代理简单使用

动态代理是一种在运行时生成代理类的机制,它允许我们在不事先知道被代理对象的具体类型的情况下创建代理对象。在动态代理中,代理类是在运行时动态生成的,而不是在编译时静态生成的。所以他最大的好处,就是动态性和灵活性。

JDK代理(提供实现类)

jdk代理演示:

package com.yimeng.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @Author yimeng* @Date 2024/9/25 23:50* @PackageName:com.yimeng.proxy* @ClassName: HelloServiceProxy* @Description: TODO* @Version 1.0*/
public class HelloServiceProxy implements InvocationHandler {/*** 真实的服务对象*/private Object target;/**** @param target* @return 绑定委托对象并返回一个代理类*/public Object bind(Object target) {this.target = target;//取得代理对象//public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException//loader服务对象的类加载器,interfaces是加载服务对象的接口,h是执行这个代理类的方法的执行者return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}/*** 通过代理对象调用方法首先进入这个方法* invoke方法的参数分别是:proxy是代理对象,method是被调用的方法,args是方法的参数*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("#########我是JDK动态代理##########");Object result = null;//反射方法前调用System.out.println("我准备说hello");result = method.invoke(target, args);//反射方法后调用System.out.println("我说过hello了");return result;}
}
package com.yimeng.service.impl;import com.yimeng.service.HelloService;/*** @Author yimeng* @Date 2024/9/25 23:49* @PackageName:com.yimeng.service.impl* @ClassName: HelloServiceImpl* @Description: TODO* @Version 1.0*/
public class HelloServiceImpl implements HelloService {@Overridepublic void sayHello(String name) {System.out.println("hello "+name);}
}
package com.yimeng.service;/*** @Author yimeng* @Date 2024/9/25 23:49* @PackageName:com.yimeng.service* @ClassName: HelloService* @Description: TODO* @Version 1.0*/
public interface HelloService {public void sayHello(String name);
}
package com.yimeng;import com.yimeng.proxy.HelloServiceProxy;
import com.yimeng.service.HelloService;
import com.yimeng.service.impl.HelloServiceImpl;/*** @Author yimeng* @Date 2024/9/25 23:51* @PackageName:com.yimeng* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {HelloServiceProxy HelloHandler = new HelloServiceProxy();HelloService proxy = (HelloService)HelloHandler.bind(new HelloServiceImpl());proxy.sayHello("张三");}
}

结果:

在这里插入图片描述

使用关键:

  1. 创建一个类作为代理类,实现InvocationHandler接口,并重写invoke方法。在 invoke 方法中我们一般会调用原生方法(被代理类的方法)并自定义一些增强的逻辑;(当然也可以不调用原生方法,这样就相当于是替代了,不是增强了,看你想怎么用吧)

  2. 使用Proxy.newProxyInstance(类加载器, 被代理类实现的接口数组, InvocationHandler接口的实现类对象);创建出一个代理对象。并且创建出来的对象表面一般声明为接口那个类型。

    newProxyInstance()方法需要三个参数:

    1. 类加载器:类加载器对象。不一定要是"目标对象.getClass().getClassLoader()"才能获取到类加载器,任何一个非java内置类的对象A,都可以通过对象A.getClass().getClassLoader() 获取加载该对象A对应类的类加载器,并且只要获取到的类加载器可以加载目标接口的实现类就行,我们使用“目标对象.getClass().getClassLoader()”和“第三方类库中类的对象或者自定义类的对象.getClass().getClassLoader()”获取到的类加载器都是一个,因为目标对象一般也是我们自定义的类或者第三方类库的类,只要是自定义的类或者第三方类库的类都是用一个类加载器加载的。为什么说不能是java内置类的对象呢?因为java内置类是由特定类加载器Bootstrap ClassLoader加载的(这里其实有涉及到类加载器的知识,什么双委派机制等,这里不展开讲)。java内置类是由 Bootstrap ClassLoader 加载的,而 Bootstrap ClassLoader 由于是使用本地代码实现的,所以它不是真正的 Java 类加载器实例,也不能用于动态代理,所以说不能用java内置类的对象.getClass().getClassLoader()
    2. 接口数组:被代理类实现的接口数组。目标对象.getClass().getInterfaces()可以获取到目标对象实现的所有接口们。目标对象实现的所有接口的所有方法都会被增强。
    3. 处理器:InvocationHandler接口的实现类对象。到时候执行被代理方法的时候,就是执行InvocationHandler接口的实现类对象的invoke方法的。
  3. 使用这个声明为接口类型但实际为代理对象的这个变量调用接口被增强的方法。

注意:

在这里插入图片描述

在这里插入图片描述

这里的代理是,对原来的业务进行增强。

JDK动态代理和静态代理的区别:JDK动态代理用到了反射,即,代理类编译的时候不需要知道要代理谁,去执行什么方法,但是,运行的时候,程序能判断出来去代理那个方法了。静态代理是,编译的时候就需要知道要代理哪个类,执行什么方法了。

JDK代理(不提供实现类)

JDK代理,代理的是接口,那么可以想一想,既然代理的是接口,那如果没有实现类怎么办,能不能代理。答案是可以的,Mybatis就是这样的。

例子:

package com.yimeng;/*** @Author yimeng* @Date 2024/10/6 10:13* @PackageName:PACKAGE_NAME* @ClassName: com.yimeng.IHello* @Description: TODO* @Version 1.0*/
public interface IHello {String say(String aa);
}
package com.yimeng;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @Author yimeng* @Date 2024/10/6 10:14* @PackageName:com.yimeng* @ClassName: FacadeProxy* @Description: TODO* @Version 1.0*/
public class FacadeProxy implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args){System.out.println("接口方法调用开始");//执行方法System.out.println("method toGenericString:"+method.toGenericString());System.out.println("method name:"+method.getName());System.out.println("method args:"+(String)args[0]);System.out.println("接口方法调用结束");return "调用返回值";}public  <T> T bind(Class<T> mapperInterface) {// 也可以使用接口来获取类加载器ClassLoader classLoader = mapperInterface.getClassLoader();Class<?>[] interfaces = new Class[]{mapperInterface};return (T) Proxy.newProxyInstance(classLoader, interfaces, this);}
}
package com.yimeng;/*** @Author yimeng* @Date 2024/10/6 10:14* @PackageName:com.yimeng* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {FacadeProxy facadeProxy=new FacadeProxy();IHello hello = facadeProxy.bind(IHello.class);System.out.println(hello.say("hello world"));}
}

结果:

在这里插入图片描述

看到,这里没有传实现类,结果也是一样可以的。

在invoke方法中少了result = method.invoke(target, args);而已,就没有调用实现类的方法了,因为根本没有实现类嘛。

对于创建代理对象的方法中(这里是bind方法,其实叫什么方法名都行),我们看到和原来有实现类的做法也是类似的,都是通过Proxy.newProxyInstance方法来创建代理对象的。就是使用的参数有一些区别:

  1. 获取类加载,这里使用了接口来获取类加载器(接口也可以获取类加载器,这一点我之前不知道)(不一定要用那个要增强的接口,随便用一个你自定义类或者第三方类库的类来获取类加载器也行,反正只是为了获取类加载器而已)
  2. 直接使用接口来创建数组了。之前是使用“实现类对象.getClass().getInterfaces()”来获取这个实现类实现的所有接口,然后对所有接口中的方法进行增强。我们直接使用接口来创建数组,那么只有这个数组中的接口的方法才能得到增强。
  3. 第三个参数还是一样

mybatis其实就是通过这里这样的做法,拿到接口的方法上面的信息,知道代理对象中的对应方法应该完成什么样的功能的。

CGLib代理

没有引入spring的情况下,需要单独引入cglib架包。spring3.2以后的环境下,不用单独引入cglib架包了,因为从spring3.2以后,cglib.jar已经被spring项目的jar包集成进去了。

这里我使用非spring环境执行的,所以需要引入jar包:

<dependencies><dependency><groupId>cglib</groupId><artifactId>cglib-nodep</artifactId><version>3.2.4</version></dependency>
</dependencies>
package invocation;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;/*** @Author yimeng* @Date 2024/9/28 23:24* @PackageName:invocation* @ClassName: LawInterceptor* @Description: TODO* @Version 1.0*/
public class LawInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// MethodInterceptor#intercept拦截方法的实现逻辑时,最好指定你想要增强目标对象的哪些方法,否则CGLIB默认会增强目标对象及其父类的所有非final方法、非private方法。// 指定要增强的方法也很简单,只要使用方法名来判断一般就可以了,如果有重载,那么你把参数也加上判断就可以了。if(isMethodsThatNeedToBeEnhanced(method)){// 增强的内容System.out.print("`律师向原告了解案情,并代替`");// 调用被代理对象的方法(被增强前的方法)Object result = methodProxy.invokeSuper(o, objects);// 如果要改造增强后方法的返回值,那么可以这样做return "被增强的collect(String evidence)返回值,替代了增强前方法的返回值";// 如果要使用增强方法的返回值作为增强后方法的返回值,下面这样直接返回就行了// return result;}else {// 不增强(原来类所有的非final方法、非private方法都会被增强,为了让不想增强的保持原样,我们可以这样,就让增强后的效果是:执行原来方法,并返回增强后的方法返回值就行了)Object result = methodProxy.invokeSuper(o, objects);return result;}}// 判断是否是需要增强的方法。通过方法名和形参来判断就行了private boolean isMethodsThatNeedToBeEnhanced(Method method) {// 判断方法名称是否为"collect"或"lawsuit"if (!"collect".equals(method.getName()) && !"lawsuit".equals(method.getName())) {return false;}// 获取方法的参数类型数组Class<?>[] parameterTypes = method.getParameterTypes();if ("collect".equals(method.getName())) {
//            // 如果目标方法 "collect" 接受一个 String 和一个 int 作为参数。那么可以下面这样写:
//            return Arrays.equals(parameterTypes, new Class<?>[]{String.class, int.class});// 这里我们目标方法 "collect" 只接受一个 String 参数,所以这样写// 判断参数类型是否匹配return Arrays.equals(parameterTypes, new Class<?>[]{String.class});}// 假设目标方法 "lawsuit" 不接受任何参数if ("lawsuit".equals(method.getName())) {// 判断是否是无参数方法return parameterTypes.length == 0;}return false;}
}
package service.impl;import service.LawEvidence;/*** @Author yimeng* @Date 2024/9/28 23:21* @PackageName:service.impl* @ClassName: LawEvidenceImpl* @Description: TODO* @Version 1.0*/
public class LawEvidenceImpl implements LawEvidence {@Overridepublic String collect(String evidence)  {System.out.println(evidence);return "没有被增强的collect(String evidence)返回值";}@Overridepublic String collect() {System.out.println("没有被增强的collect方法执行了");return "没有被增强的collect()返回值,没有被增强";}
}
package service.impl;import service.Lawsuit;/*** @Author yimeng* @Date 2024/9/28 23:22* @PackageName:service.impl* @ClassName: LawsuitImpl* @Description: TODO* @Version 1.0*/
public class LawsuitImpl implements Lawsuit {@Overridepublic void lawsuit() {System.out.println("原告打官司");}
}
package service;/*** @Author yimeng* @Date 2024/9/28 23:20* @PackageName:service* @ClassName: LawEvidence* @Description: TODO* @Version 1.0*/
public interface LawEvidence {String collect(String evidence);String collect();
}
package service;/*** @Author yimeng* @Date 2024/9/28 23:22* @PackageName:service.impl* @ClassName: Lawsuit* @Description: TODO* @Version 1.0*/
public interface Lawsuit {void lawsuit();
}
import invocation.LawInterceptor;import net.sf.cglib.proxy.Enhancer;
import service.impl.LawEvidenceImpl;
import service.impl.LawsuitImpl;/*** @Author yimeng* @Date 2024/9/28 23:15* @PackageName:PACKAGE_NAME* @ClassName: Main* @Description: TODO* @Version 1.0*/
public class Main {public static void main(String[] args) {Enhancer evidenceEnhancer = new Enhancer();evidenceEnhancer.setSuperclass(LawEvidenceImpl.class);evidenceEnhancer.setCallback(new LawInterceptor());LawEvidenceImpl evidenceProxy = (LawEvidenceImpl) evidenceEnhancer.create();System.out.println(evidenceProxy.collect("原告提供证据"));System.out.println(evidenceProxy.collect());Enhancer lawsuitEnhancer = new Enhancer();lawsuitEnhancer.setSuperclass(LawsuitImpl.class);lawsuitEnhancer.setCallback(new LawInterceptor());LawsuitImpl lawsuitProxy=(LawsuitImpl)lawsuitEnhancer.create();lawsuitProxy.lawsuit();}
}

结果:

在这里插入图片描述

MethodInterceptor接口的intercept方法有4个参数

  • obj: 增强的对象
  • method: 被拦截的方法
  • objects: 被拦截方法的参数
  • methodProxy: java.lang.reflect.Method类的代理类,可以实现对目标类方法的调用

MethodInterceptor接口的intercept方法的返回值

在 CGLIB 的 MethodInterceptor 拦截器中,intercept() 方法的返回值决定了被代理对象的增强方法(即被拦截的方法)执行后的返回结果。

intercept() 方法的返回值的作用:

  1. 传递实际返回值: 通常情况下,你会调用 methodProxy.invokeSuper() 来执行原始目标方法,并将其结果返回。这样,代理对象的方法调用和未代理时的调用行为保持一致,返回原本应该返回的值。例如,如果目标方法返回一个计算结果或者查询的值,拦截器的返回值就成为调用方接收到的值。
  2. 修改返回结果: 你可以通过 intercept() 方法拦截目标方法,执行某些自定义逻辑,然后根据需求修改目标方法的返回值。例如,如果目标方法返回一个数字,你可以在拦截器中对该数字进行修改,然后返回一个不同的值。
  3. 阻止目标方法的执行: 如果你不调用 methodProxy.invokeSuper(),而是直接返回某个值,这样可以避免实际调用目标方法。这种情况下,目标方法的逻辑会被完全跳过,调用方只会接收到你返回的值,而不会知道原始方法是否被执行。

常见的几种使用方式:

1. 直接返回目标方法的执行结果:

这是最常见的使用方式,拦截器增强方法后,将原方法的结果返回。

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 执行目标方法并返回其结果Object result = methodProxy.invokeSuper(o, args);return result;
}
2. 修改返回值:

你可以在目标方法执行后修改其返回值。

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 执行目标方法Object result = methodProxy.invokeSuper(o, args);// 如果返回的是一个数字,可以在这里做修改if (result instanceof Integer) {result = (Integer) result + 100; // 修改返回值}return result;
}
3. 阻止方法执行并返回自定义值:

如果你不希望目标方法被执行,直接返回一个自定义值即可。

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 如果方法名称为某个特定值,直接返回自定义的结果if ("someMethod".equals(method.getName())) {return "Method execution was intercepted!"; // 自定义返回值}// 否则执行原方法return methodProxy.invokeSuper(o, args);
}

总结:

  • 如果你希望目标方法执行,并将结果返回给调用方,intercept() 方法需要返回 methodProxy.invokeSuper() 的结果。
  • 如果你想修改或替换返回结果,则可以在 intercept() 方法中自定义返回值。
  • 如果你不希望目标方法执行,直接返回你自定义的值,这样目标方法的逻辑将不会被触发。

注意事项:

  • MethodInterceptor接口里如果想要调用原目标对象的方法,必须使用methodProxy#invokeSuper方法,而不是使用methodProxy#invoke方法。如果使用invoke方法会发生无限循环调用的问题,你可以简单理解为invokeSuper是调用的目标对象的原方法,而invoke是调用的代理对象的增强方法,这就导致了程序再一次进入到增强的拦截方法intercept里,周而复始。

    在这里插入图片描述

  • 在写MethodInterceptor#intercept拦截方法的实现逻辑时,最好指定你想要增强目标对象的哪些方法,如果不指定,那么CGLIB默认会增强目标对象及其父类的所有非final方法、非private方法。

  • JDK的增强方式,代理类是实现InvocationHandler接口,而CGLIB的增强方式代理类是实现MethodInterceptor接口

  • CGLIB不是通过Java反射去做到对目标方法的动态调用的。CGLIB是采用FastClass方式去调用目标方法的,这个方式比反射快得多,反射比较慢是因为,反射需要,经过一系列权限校验、JVM方法区查找方法定义、native方法调用等动作,这些动作都是比较慢的。但是,FastClass方式调用目标对象的方法就类似于直接调用某个具体对象的方法,就不需要经过反射那些繁重的步骤了,FastClass底层其实是通过一系列规则,让invokeSuper方法能找到要执行的目标方法,然后去执行对应的方法就行了,就类似于直接调用目标方法,所以速度很快。

    总之:FastClass方式算是一种技巧层面的东西,他通过在java内存里边维护一个index值和目标对象的方法之间的逻辑映射,然后运行期可以根据index和实例来动态调用方法、且不用使用比较“重”的Java反射功能。为什么说这个是利用了FastClass的方式来调用目标方法的,其实是因为,CGLib生成的代理类我们通过反编译可以发现,他们其实都是继承FastClass这个类的,并且重写了FastClass的getIndex()方法和invoke()方法。

研究

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

CGLIB(Code Generation Library)是一个基于ASM(Java 字节码操作和分析框架)的字节码生成库,用于在运行时动态生成 Java 类的子类,实现动态代理的功能。CGLIB 动态代理是一种基于类的动态代理方式,不需要接口。

CGLIB 动态代理的原理如下:

  1. 首先,CGLIB 通过动态地创建一个继承被代理类的子类。
  2. 然后在子类中,通过方法拦截器(MethodInterceptor)来拦截被代理类的方法调用。
  3. 当调用代理对象的方法时,子类会委托给方法拦截器处理,在处理逻辑中可以加入我们自己的增强逻辑。
  4. 最后,方法拦截器可以决定是否调用被代理类的原始方法或直接返回结果。

CGLIB 动态代理的优点主要是:

  1. 不需要被代理类实现接口:相比基于接口的JDK动态代理,CGLIB 可以代理没有实现任何接口的类。
  2. CGLIB动态代理比JDK动态代理快。使用CGLib去执行增强的方法时,他去执行原有方法的时候,不是通过反射去执行的,而是使用FastClass机制,类似于直接调用的(和我们自己手动写了一个静态代理一样),所以速度快。反射的话,需要经过一系列的权限验证、通过native方法请求jvm去方法区查找方法定义、以及最后的invoke仍然可能要通过JNI调用native方法,所以速度会比较慢。

CGLIB 动态代理也有一些限制和注意事项:

  1. 无法代理 final 方法和 final 类。
  2. 对于私有方法和方法实现为 final 的方法,也无法直接代理。
  3. 对于静态方法,CGLIB 会直接调用原始方法,不会进行代理。

在这里插入图片描述

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

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

相关文章

C++基类构造器的自动调用

C基类构造器的自动调用 虽然基类的构造器和解构器不会被派生类继承&#xff0c;但它们会被派生类的构造器和解构器自动调用&#xff0c;今天我们用代码实证一下。 验证代码 源代码&#xff0c;仔细看注释内容&#xff1a; D:\YcjWork\CppTour>vim c2004.cpp #include &l…

《RabbitMQ篇》基本概念介绍

MQ功能 解耦 MQ允许不同系统或组件之间松散耦合。发送者和接收者不需要直接连接&#xff0c;从而提高了系统的灵活性和可维护性。异步处理 使用MQ可以实现异步消息传递&#xff0c;发送者可以将消息放入队列后立即返回&#xff0c;不必等待接收者处理。这提高了系统的响应速度…

Windows下Jenkins控制台中文乱码

问题描述 问题情况如下图&#xff1a; 环境信息 Windows 11 家庭中文版java 21.0.4 2024-07-16 LTSJenkins 2.452.3 解决方法 增加系统JAVA_TOOL_OPTIONS&#xff0c;并设置值为-Dfile.encodingGBK。 打开设置方法&#xff1a;桌面上右键点击“此电脑”图标&#xff0c;选…

算法笔记(十三)——BFS 解决最短路问题

文章目录 迷宫中离入口最近的出口最小基因变化单词接龙为高尔夫比赛砍树 BFS 解决最短路问题 BFS(广度优先搜索) 是解决最短路径问题的一种常见算法。在这种情况下&#xff0c;我们通常使用BFS来查找从一个起始点到目标点的最短路径。 迷宫中离入口最近的出口 题目&#xff1a;…

Android SystemUI组件(11)SystemUIVisibility解读

该系列文章总纲链接&#xff1a;专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节持续迭代之前章节思维导图&#xff0c;主要关注左侧最上方SystemUiVisibility解读部分即可。 本章节主要讲解SystemUiVisibility的概念及其相…

数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(一).创建基础表

一. 使用工具和命令 1.1 使用的工具 Navicat Premium 17 &#xff1a;“Navicat”是一套可创建多个连接的数据库管理工具。 MySQL版本8.0.39 。 1.2 使用的命令 Navicat中使用的命令 命令命令解释SHOW DATABASES&#xff1b;展示所有的数据库CREATE DATABASE 数据库名称; 创…

thinkphp 学习记录

1、PHP配置 &#xff08;点开链接后&#xff0c;往下拉&#xff0c;找到PHP8.2.2版本&#xff0c;下载的是ZIP格式&#xff0c;解压即用&#xff09; PHP For Windows: Binaries and sources Releases &#xff08;这里是下载地址&#xff09; 我解压的地址是&#xff1a;D:\…

1、如何查看电脑已经连接上的wifi的密码?

在电脑桌面右下角的如下位置&#xff1a;双击打开查看当前连接上的wifi的名字&#xff1a;ZTE-kfdGYX-5G 按一下键盘上的win R 键, 输入【cmd】 然后&#xff0c;按一下【回车】。 输入netsh wlan show profile ”wifi名称” keyclear : 输入完成后&#xff0c;按一下回车&…

中断系统的原理

一、介绍 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的。中断是指‌CPU在正常运行程序时&#xff0c;由于内部或外部事件的发生&#xff0c;导致CPU中断当前运行的程序&#xff0c;转而去执行其他程序的过程。‌ 中断可以是硬件产生的&#xff0c;也可以是…

安全运营中心 (SOC) 团队对其安全工具感到失望

Vectra AI 表示&#xff0c;安全运营中心 (SOC) 从业人员认为&#xff0c;由于太多孤立的工具和缺乏准确的攻击信号&#xff0c;他们在检测和确定真实威胁的优先级方面正在失败。 人们对供应商的不信任感日益加深&#xff0c;认为供应商的工具在发现真正的攻击方面起的阻碍作用…

金纳米星“融入”水凝胶,原位生长的奥秘,应用前景的探索

大家好&#xff01;今天来了解一项在三维水凝胶表面生长金纳米星的研究——《Growing Gold Nanostars on 3D Hydrogel Surfaces》发表于《Chemistry of Materials》。水凝胶在生物医学等诸多领域有着重要应用&#xff0c;而金纳米星具有独特的光学性质。这项研究通过原位合成的…

【Linux】线程与线程安全知识总结

向外张望的人在做梦&#xff0c; 向内审视的人才是清醒的。 --- 荣格 --- 我最近复习了线程安全这部分知识&#xff0c;将不明白的问题总结出来&#xff0c;并通过AI进行问答帮助我进行学习巩固。本人能力有限 &#xff0c;可能有些内容不准确&#xff0c;望各位大佬海涵&am…

【前端开发入门】css快速入门

目录 引言一、css盒模型1. 盒模型概念2. 盒模型案例 二、css编写1. html文件内部编写1.1 标签style属性编写1.2 css选择器关联1.2.1 id选择器1.2.2 class选择器1.2.3 标签选择器1.2.4 css选择器作用域1.2.5 其他选择器1.2.6 各css选择器优先级 2. 单独维护css文件2.1 创建css文…

HDLBits中文版,标准参考答案 | 3.1.4 Karnaugh Map to Circuit | 卡诺图到电路

关注 望森FPGA 查看更多FPGA资讯 这是望森的第 11 期分享 作者 | 望森 来源 | 望森FPGA 目录 1 3 变量 2 4 变量 3 4 变量 4 4 变量 5 最小 SOP 和 POS 6 卡诺图 7 卡诺图 8 使用多路复用器实现的卡诺图 本文中的代码都能够正常运行&#xff0c;请放心食用&#x1f…

最强AI绘画大模型Flux可以在SDWebUI 上使用了!超便捷的Flux模型使用教程!AI绘画零基础入门到实战教程

大家好&#xff0c;我是画画的小强 目前最强的AI绘画大模型Flux.1 横空出世有段时间了&#xff0c;模型效果也得到了广泛的认可&#xff0c;但是 Stable Diffusion WebUI 官方迟迟没有跟进&#xff0c;据说是因为要修改很多底层的处理机制&#xff0c;加之ComfyUI如火如荼&…

Nginx的基础讲解之重写conf文件

一、Nginx 1、什么是nginx&#xff1f; Nginx&#xff08;engine x&#xff09;是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。 2、用于什么场景 Nginx适用于各种规模的网站和应用程序&#xff0c;特别是需要高并发处理和负载均衡的场…

【React】事件机制

事件机制 react 基于浏览器的事件机制自身实现了一套事件机制&#xff0c;称为合成事件。比如&#xff1a;onclick -> onClick 获取原生事件&#xff1a;e.nativeEvent onClick 并不会将事件代理函数绑定到真实的 DOM节点上&#xff0c;而是将所有的事件绑定到结构的最外层…

Pikachu-目录遍历

目录遍历&#xff0c;跟不安全文件上传下载有差不多&#xff1b; 访问 jarheads.php 、truman.php 都是通过 get 请求&#xff0c;往title 参数传参&#xff1b; 在后台&#xff0c;可以看到 jarheads.php 、truman.php所在目录&#xff1a; /var/www/html/vul/dir/soup 图片…

master节点k8s部署]33.ceph分布式存储(四)

总结ceph分布式存储&#xff08;三&#xff09;中提到的三种方法&#xff1a; 1.创建rbda&#xff0c;并且在创建pv的时候配置该rbda,以下代码仅展示关键信息。 [rootxianchaomaster1 ~]# cat pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: ceph-pv ...…

【每日一题 | 24.10.7】Fizz Buzz 经典问题

1. 题目2. 解题思路3. 代码实现&#xff08;AC_Code&#xff09; 个人主页&#xff1a;C_GUIQU 归属专栏&#xff1a;每日一题 1. 题目 Fizz Buzz 经典问题 2. 解题思路 【法1】逻辑硬解&#xff1a;按照题目逻辑分四种情况&#xff0c;用if else 判断即可。 【法2】switc…