深入了解JDK动态代理

什么是JDK动态代理

        (有动态代理,就有静态代理,参见:多线程03--静态代理模式_runnable接口静态代理模式-CSDN博客)

        JDK动态代理是Java提供的一种动态生成代理对象的机制,允许在运行时创建一个实现了指定接口的代理类。它主要用于在不修改目标对象的情况下,为目标对象添加额外的功能,比如日志记录、权限检查、事务管理等。

编码实现

        代码比较简单,但核心的步骤及实现细节都有,主要分析在后面。

定义一个顶层接口

package com.ldzn.normal.proxy.dynamic;

/**
 * @FileName: IService
 * @Description: 基类接口
 * @Author: liulianglin
 * @Date: 2024/11/26:11:32
 */
public interface IService {
    void myselfMethod();
}

被代理的对象

package com.ldzn.normal.proxy.dynamic;

/**
 * @FileName: RealObjectService
 * @Description: 真的对象服务
 * @Author: liulianglin
 * @Date: 2024/11/26:11:33
 */
public class RealObjectService implements IService{

    @Override
    public void myselfMethod() {
        System.out.println("RealObjectService test .......");
    }

}

自定义个动态代理处理类

package com.ldzn.normal.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @FileName: DynamicInvocationHandler
 * @Description: 动态代理处理类
 * @Author: liulianglin
 * @Date: 2024/11/26:11:34
 *
 *   1.拿到被代理对象的引用,然后获取它的接口
 *   2.jdk代理重新生成一个类,同时实现我们个额的代理对象所实现的接口
 *   3.把被代理对象的引用也拿到了
 *   4.重新动态生成一个class字节码
 *   5.然后编译
 *
 */
public class DynamicInvocationHandler implements InvocationHandler {
    private Object target;

    public Object getInstance(IService target) throws Exception {
        this.target = target;
        Class clazz = target.getClass();
        System.out.println("被代理对象的class是:"+ clazz);
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);

    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始进行方法增强,(记录日志、校验、性能统计等),,,,");
        Object result = method.invoke(target, args);
        System.out.println("方法增强完毕,(记录日志、校验、性能统计等),,,,,");
        return result;
    }
}

测试调用

package com.ldzn.normal.proxy.dynamic;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @FileName: MainTest
 * @Description:
 * @Author: liulianglin
 * @Date: 2024/11/26:12:10
 */
public class MainTest {

    public static void printClassInfo(Executable[] targets) {
        for (Executable target : targets) {
            // 构造器/方法名称
            String name = target.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            // 拼接左括号
            sBuilder.append('(');
            Class<?>[] clazzParams = target.getParameterTypes();
            // 拼接参数
            for (Class<?> clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(',');
            }
            //删除最后一个参数的逗号
            if (clazzParams.length != 0) {
                sBuilder.deleteCharAt(sBuilder.length() - 1);
            }
            //拼接右括号
            sBuilder.append(')');
            //打印 构造器/方法
            System.out.println(sBuilder.toString());
        }
    }

    /**
     * 将动态代理生成的代理类的字节码保存到本地磁盘,方便调试查看
     * @param path
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IOException
     */
    public static void saveProxyClassFile(String path)  {

        Class cl = null;
        FileOutputStream fileOutputStream = null;
        try {
            // Java11开始ProxyGenerator,不再public,改为了private,无法直接使用,所以采用反射的方式获取它
            cl = Class.forName("java.lang.reflect.ProxyGenerator");

            Method m =cl.getDeclaredMethod("generateProxyClass",String.class,Class[].class);
            m.setAccessible(true);
            byte[] $proxy1 = (byte[]) m.invoke(null, "$proxy1",
                    RealObjectService.class.getInterfaces());
            System.out.println($proxy1.length);

            fileOutputStream = new FileOutputStream((path + "$Proxy.class"));
            fileOutputStream.write($proxy1);
        }catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (fileOutputStream !=null) {
                try {
                    fileOutputStream.flush();
                    fileOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }


    public static void main(String[] args) {
        try {
            IService iService = (IService) new DynamicInvocationHandler().getInstance(new RealObjectService());
            iService.myselfMethod();

            // 将生成的动态代理类的字节码文件保存到本地
            // saveProxyClassFile("G:\\JAVA_workspace\\concurrent_programming\\");

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

动态代理类字节码文件分析

动态代理类字节码的生成与加载过程

        动态代理的本质就是在运行时动态的生成一个代理类,这个代理类的加载同样遵循JVM类加载机制那一套东西,涉及到动态生成代理类的字节码并将其加载到JVM中。下面是它的大概流程(下面的内容看着可能暂时会比较懵逼!!! 先混个眼熟,对过程有个大概了解,后面源码分析章节会详细分析,再整体串起来):

1. 确定类加载器

        首先,Proxy.newProxyInstance 方法接收一个 ClassLoader 参数,这个参数指定了用于加载代理类的类加载器。通常,这个类加载器是目标对象的类加载器,但也可以是其他任意的类加载器。

2. 生成代理类的字节码(核心)

JDK动态代理通过以下关键步骤生成代理类的字节码:

  • 创建代理类名称:代理类的名称是由 "$Proxy" + 一个数字序号 构成的,例如 $Proxy0$Proxy1 等。这个序号是为了保证代理类的名称唯一性。

  • 实现接口:代理类实现了传入 newProxyInstance 方法的所有接口。

  • 添加构造方法:代理类有一个私有的构造方法,它接受一个 InvocationHandler 类型的参数。

  • 生成方法实现:对于每个接口方法,代理类都会生成一个实现,该实现在调用目标方法之前和之后会调用 InvocationHandlerinvoke 方法。

  • 字节码生成:JDK内部使用 ProxyBuilder 类(在 JDK 内部,不是公开API)来生成代理类的字节码。

3. 加载代理类(JVM的那一套)

        生成的字节码被传递给类加载器,由类加载器负责将字节码加载到JVM中,创建代理类的 Class 对象。这个过程包括:

  • 定义类:类加载器使用 defineClass 方法将字节码定义为一个类。

  • 链接类:链接过程包括验证字节码、准备类、解析符号引用等步骤。

  • 初始化类:对于非抽象的代理类,JVM会执行类的初始化,包括执行类构造器 <clinit>()

4. 创建代理实例(使用过程)

        一旦代理类被加载和链接,就可以使用 newProxyInstance 方法返回的 Constructor 对象来创建代理实例。这个构造器的 newInstance 方法接收一个 InvocationHandler 实例作为参数,并返回代理对象。

5. 方法调用和拦截(使用过程)

        当代理对象的方法被调用时,JVM会跳转到代理类中的方法实现,这些实现会委托给 InvocationHandlerinvoke 方法来处理。invoke 方法负责执行实际的逻辑,包括调用目标对象的方法和添加额外的处理。

字节码文件生成

/**
     * 将动态代理生成的代理类的字节码保存到本地磁盘,方便调试查看
     * @param path
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IOException
     */
    public static void saveProxyClassFile(String path)  {

        Class cl = null;
        FileOutputStream fileOutputStream = null;
        try {
            // Java11开始ProxyGenerator,不再public,改为了private,无法直接使用,所以采用反射的方式获取它
            cl = Class.forName("java.lang.reflect.ProxyGenerator");

            Method m =cl.getDeclaredMethod("generateProxyClass",String.class,Class[].class);
            m.setAccessible(true);
            byte[] $proxy1 = (byte[]) m.invoke(null, "$proxy1",
                    RealObjectService.class.getInterfaces());
            System.out.println($proxy1.length);

            fileOutputStream = new FileOutputStream((path + "$Proxy.class"));
            fileOutputStream.write($proxy1);
        }catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (fileOutputStream !=null) {
                try {
                    fileOutputStream.flush();
                    fileOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }

字节码文件示例

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.ldzn.normal.proxy.dynamic.IService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $proxy1 extends Proxy implements IService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $proxy1(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void myselfMethod() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.ldzn.normal.proxy.dynamic.IService").getMethod("myselfMethod");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(((Throwable)var2).getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(((Throwable)var3).getMessage());
        }
    }
}

字节码文件分析

        从上面的字节码文件中可以看到,生成的动态代理类继承了Proxy类,并实现了IService接口(我们自己的顶层接口),在它的static代码块中(即Class文件中),通过反射的方式为其内部的多个Method对象赋值,这些方法即包括Object基类包含的一些方法(如hashcode等),最关键的包含我们的IService中的myselfMethod方法(引用为m3)。同时动态代理类重写了Object基类中的方法和我们IService中的myselfMethod方法,现在看myselfMethod方法的实现,它调用super.h.invoke(this, m3, (Object[])null);  这里的super指的是Proxy父类,h表示Proxy父类的InvocationHandler变量。


这里可能有几个疑问(后边会回过头再一一回答):
1)Proxy父类的InvocationHandler变量是什么时候赋值的?

2)这个invoke执行的是谁的方法?

源码分析

newProxyInstance源码分析(入口)

public static Object newProxyInstance(ClassLoader loader,
                                    Class<?>[] interfaces,
                                    InvocationHandler h) {
    // 确保传入的 InvocationHandler 对象不为 null,如果为 null 则抛出 NullPointerException
    Objects.requireNonNull(h);

    // 如果存在安全管理器,则获取调用者的类对象(即调用 newProxyInstance 方法的类的类对象)
    // 否则,caller 设置为 null
    final Class<?> caller = System.getSecurityManager() == null
                                ? null
                                : Reflection.getCallerClass();

    /*
     * 查找或生成指定的代理类及其构造器。
     * 这个方法会检查是否已经缓存了对应的代理类,如果没有,则会生成一个新的代理类
     * 并缓存它。生成的代理类实现了传入的 interfaces 数组中声明的所有接口。
     */
    Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

    // 使用查找或生成的构造器创建一个新的代理实例
    // 这个方法会使用反射来调用代理类的构造器,传入的 InvocationHandler实例h会被赋值为Proxy的h变量
    return newProxyInstance(caller, cons, h);
}

        使用Proxy.newProxyInstance方法时,JVM会获取代理类的构造函数,并将InvocationHandler实例作为参数传入构造函数,后面用这个构造方法来实例化代理对象 。 

getProxyConstructor 源码分析

private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // optimization for single interface
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            if (caller != null) {
                checkProxyAccess(caller, loader, intf);
            }
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
            // interfaces cloned
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
                checkProxyAccess(caller, loader, intfsArray);
            }
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }

 分析:

  1. 单接口优化

    if (interfaces.length == 1) {
        Class<?> intf = interfaces[0];
        if (caller != null) {
            checkProxyAccess(caller, loader, intf);
        }
        return proxyCache.sub(intf).computeIfAbsent(
            loader,
            (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
        );
    }

           如果代理类只需要实现一个接口,代码会进行优化处理。首先,获取这个单一接口 intf。如果调用者 caller 不为 null,则调用 checkProxyAccess 方法检查是否有权限创建代理类。然后,使用 proxyCache 缓存来获取或生成代理类的构造器。proxyCache 是一个多层次的缓存结构,其中 sub(intf) 获取或创建与接口相关的子缓存,computeIfAbsent 方法确保对于给定的类加载器 loader,只生成一次代理类。

  2. 多接口处理

    } else {
        // interfaces cloned
        final Class<?>[] intfsArray = interfaces.clone();
        if (caller != null) {
            checkProxyAccess(caller, loader, intfsArray);
        }
        final List<Class<?>> intfs = Arrays.asList(intfsArray);
        return proxyCache.sub(intfs).computeIfAbsent(
            loader,
            (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
        );
    }

            如果代理类需要实现多个接口,代码会克隆接口数组以避免修改原始数组。如果调用者 caller 不为 null,则调用 checkProxyAccess 方法检查权限。然后,将接口数组转换为列表 intfs。与单接口的情况类似,使用 proxyCache 缓存来获取或生成代理类的构造器。这里,proxyCache 的子缓存是基于接口列表创建的,确保对于给定的类加载器和接口列表,只生成一次代理类。

        3.代理类构建

        核心!!!!,参加下一个章节内容

new ProxyBuilder(ld, clv.key()).build()

ProxyBuilder源码分析

构造方法:

        ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
            // 检查 JVM 的模块系统是否已经完全初始化。如果没有,会抛出 InternalError 异常,因为动态代理在模块系统未完全初始化之前是不支持的
            if (!VM.isModuleSystemInited()) {
                throw new InternalError("Proxy is not supported until "
                        + "module system is fully initialized");
            }

            // 检查传入的接口列表 interfaces 的大小是否超过了 65535,这是由于内部实现的限制。如果超过,会抛出 IllegalArgumentException 异常
            if (interfaces.size() > 65535) {
                throw new IllegalArgumentException("interface limit exceeded: "
                        + interfaces.size());
            }

            // 调用 referencedTypes 方法来获取所有被引用的类型,并存储在 Set<Class<?>> 中
            Set<Class<?>> refTypes = referencedTypes(loader, interfaces);


            // 验证代理接口是否符合 newProxyInstance 方法中指定的限制条件
            validateProxyInterfaces(loader, interfaces, refTypes);

            // 成员变量初始化
            this.interfaces = interfaces;
            this.module = mapToModule(loader, interfaces, refTypes);
            assert getLoader(module) == loader;
        }

build方法

       Constructor<?> build() {
            // 调用 defineProxyClass 方法来定义代理类。这个方法负责生成代理类的字节码,并将其加载到 JVM 中
            Class<?> proxyClass = defineProxyClass(module, interfaces);
            final Constructor<?> cons;
            try {
                /*
                 通过代理类的 Class 对象,尝试获取其构造器 Constructor。构造器的参数类型是                 InvocationHandler 类型,这是因为每个代理对象都需要一个 InvocationHandler 来处理方法调用
                
                    private static final Class<?>[] constructorParams ={InvocationHandler.class };
                */

                cons = proxyClass.getConstructor(constructorParams);
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }

            /*
            使用 AccessController.doPrivileged 来设置构造器的可访问性为 true,这样就可以在代码中创建代理实例,即使构造器是私有的
            */
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
            
            // 返回构造器
            return cons;
        }

defineProxyClass源码分析

        private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
            // 代理类的包名及访问标识
            String proxyPkg = null;     
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;  // non-public, final
                    String pkg = intf.getPackageName();
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // all proxy interfaces are public
                proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
                                       : PROXY_PACKAGE_PREFIX;
            } else if (proxyPkg.isEmpty() && m.isNamed()) {
                throw new IllegalArgumentException(
                        "Unnamed package cannot be added to " + m);
            }

            if (m.isNamed()) {
                if (!m.getDescriptor().packages().contains(proxyPkg)) {
                    throw new InternalError(proxyPkg + " not exist in " + m.getName());
                }
            }

            /*
             * 生成代理类的名称,一般是$proxy0、$proxy1等
               proxyName :  com.sun.proxy.$Proxy0
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg.isEmpty()
                                    ? proxyClassNamePrefix + num
                                    : proxyPkg + "." + proxyClassNamePrefix + num;

            // 获取类加载器
            ClassLoader loader = getLoader(m);
            trace(proxyName, m, loader, interfaces);

            /*
             *  生成代理类的字节码(关键!!)
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
            try {

                // 定义代理类并返回,JLA被用来通过反射来访问ClassLoader的defineClass方法,这是一个native方法,用于将传入的字节码转proxyClassFile换成Java类,加载到JVM中
                Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile,
                                              null, "__dynamic_proxy__");
                reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
                return pc;
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }

返回的代理类

 

答疑

还记得前面的问题吗,现在一一来揭开面纱
1)Proxy父类的InvocationHandler变量是什么时候赋值的?

        代理类的构造器需要一个InvocationHandler参数,这个参数在创建代理实例时被传递给Proxy类的构造器,从而初始化Proxy类中的h字段。具体是在newProxyInstance(caller, cons, h);方法实现的。这个方法会使用反射来调用代理类的构造器,传入的 InvocationHandler实例h会被赋值为Proxy的h变量。

2)这个invoke执行的是谁的方法

        这个invoke执行的就是DynamicInvocationHandler 中的invoke方法。

总结

        通过上面的内容描述,现在基本的脉络大致有个浅显的了解,现在再整体串一下JDK动态代理的工作原理。

1)编码层工作

        定义好顶层接口、需要代理的类、动态代理类处理器。

2)代理对象时怎么生成的(核心

        通过调用Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成的,参数说明:

  1. ClassLoader loader :类加载器,用于将最后生成的动态代理类Class文件加载进JVM中。
  2. Class<?>[] interfaces: 需要代理的类实现的所有接口。
  3. InvocationHandler h:指的就是我们实现的动态代理类处理器,里边的invoke方法用于实现对被代理对象中方法的调用和服务增强。

        在生成代理对象的过程中,大致会经历如下几个关键步骤(没有列的也不代表不重要):

  1. 通过ProxyGenerator.generateProxyClass生成动态代理类的字节码。
  2. 然后使用JLA.defineClass将字节码数组生成动态代理类的Class对象,这个创建的 Class 对象被存储在 JVM 的堆内存中。
  3. 然后调用proxyClass.getConstructor获取动态代理类的类加载器。
  4. 使用类加载器的newInstance方法生成一个动态代理类实例对象

3)代理对象是怎么工作

        当我们调用代理对象的方法时,其实代理对象中的这个方法会调用Proxy的InvocationHandler实例的invoke方法,而这个InvocationHandler就是我们在上面newProxyInstance传入的我们自己实现的动态类处理器。

public final void myselfMethod() throws  {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C#基础56-60

56.字符数组x中存有任意一串字符&#xff1b;串中的所有小写字母改写成大写字母&#xff0c;如果是大写字母改为小写字母&#xff0c;其他字符不变。最后把已处理的字符串仍重新存入字符数组x中&#xff0c;最后调用函数把结果输出到控制台中。 57.求出100以上1000以内所有个位…

华为IPD流程管理体系L1至L5最佳实践-解读

该文档主要介绍了华为IPD流程管理体系&#xff0c;包括流程体系架构、流程框架实施方法、各业务流程框架示例以及相关案例等内容&#xff0c;旨在帮助企业建立高效、规范的流程管理体系&#xff0c;实现业务的持续优化和发展。具体内容如下&#xff1a; 1. 华为流程体系概述 -…

Edge浏览器保留数据,无损降级退回老版本+禁止更新教程(适用于Chrome)

3 个月前阿虚就已经写文章告警过大家&#xff0c;Chromium 内核的浏览器将在 127 以上版本开始限制仍在使用 Manifest V2 规范的扩展&#xff1a;https://mp.weixin.qq.com/s/v1gINxg5vMh86kdOOmqc6A 像是 IDM、油猴脚本管理器、uBblock 等扩展都会受到影响&#xff0c;后续将无…

DevOps引领数字化转型新趋势

DevOps帮助数字化转型 在数字化转型的大潮中&#xff0c;DevOps作为一种文化、运动和实践&#xff0c;已经成为推动企业快速适应市场变化、提高竞争力的关键因素。DevOps的核心在于打破开发&#xff08;Development&#xff09;和运维&#xff08;Operations&#xff09;之间的…

ctfshow

1,web21 Basic认证采用Base64加密方式&#xff0c;Base64解码字符串发现是 用户名:密码 的格式进行Base64编码。 密码shark63 2,web22 用 子域名扫描器 扫出flag.ctf.show拿到flag&#xff0c;但这个域名已经没了所以就直接交的官方提供的flag。 3,web23 这段PHP代码是一个简单…

从 0 到 1 掌握部署第一个 Web 应用到 Kubernetes 中

文章目录 前言构建一个 hello world web 应用项目结构项目核心文件启动项目 检查项目是否构建成功 容器化我们的应用编写 Dockerfile构建 docker 镜像推送 docker 镜像仓库 使用 labs.play-with-k8s.com 构建 Kubernetes 集群并部署应用构建 Kubernetes 集群环境编写部署文件 总…

数据结构与算法——1120——时间空间效率问题求边界值

目录 1、效率问题 1、时间复杂度 1、O(1) 2、O(n) 3、O(n) 或O(n*log2n)——n倍的log以2为底n的对数 例题 4、O(n) 2、空间复杂度 3、数组和链表 2、面试题之求边界值 题目 解答 &#xff08;1&#xff09;-i &#xff08;2&#xff09;~i &#xff08;3&#x…

爬虫与反爬-Ja3指纹风控(Just a moment...)处理方案及参数说明

概述&#xff1a;本文将针对 Ja3 指纹检测风控进行处理&#xff0c;举例了一个案例并使用两种不同的破解方案进行突破&#xff0c;同时深入了解指纹间不同字符所代表的含义 指纹检测背景&#xff1a; 1、每一个设备、软件都有独属于自己的设备信息、版本号、加密算法、椭圆算…

【JUC-JMM】Java Memory Model Java内存模型

Java内存模型--JMM 一、JMM是什么&#xff1f;二、Happens-Before原则三、JMM有什么用&#xff1f; 一、JMM是什么&#xff1f; JMM&#xff0c;全拼Java Memory Model&#xff0c;翻译过来就是Java内存模型。 那么&#xff0c;我们不禁思索&#xff0c;Java内存模型有什么用&…

SpringAI:Java 开发的智能新利器

一、SpringAI 简介 随着人工智能技术的飞速发展&#xff0c;越来越多的开发者开始探索如何将 AI 能力集成到现有的应用中来提升产品的智能化水平。Spring AI 正是为 Java 开发者提供的一款强大的 AI 框架&#xff0c;使得这一集成过程变得前所未有的简单和高效。 本文将深入探…

在Excel中处理不规范的日期格式数据并判断格式是否正确

有一个Excel表&#xff0c;录入的日期格式很混乱&#xff0c;有些看着差不多&#xff0c;但实际多一个空格少一个字符很难发现&#xff0c;希望的理想格式是 1980-01-01&#xff0c;10位&#xff0c;即&#xff1a;“YYYY-mm-dd”&#xff0c;实际上数据表中这样的格式都有 19…

【我在CSDN成长】我的五周年创作纪念日

感叹 五年的时光匆匆而过&#xff0c; 像一阵风&#xff0c;拂过岁月的湖面&#xff0c; 泛起层层涟漪&#xff0c;又悄然离去。 曾经的欢笑与泪水&#xff0c; 那些奋斗的日夜&#xff0c; 如同电影般在脑海中放映&#xff0c; 却已成为遥远的回忆。 五年&#xff0c;说长不长…

Jmeter中的定时器

4&#xff09;定时器 1--固定定时器 功能特点 固定延迟&#xff1a;在每个请求之间添加固定的延迟时间。精确控制&#xff1a;可以精确控制请求的发送频率。简单易用&#xff1a;配置简单&#xff0c;易于理解和使用。 配置步骤 添加固定定时器 右键点击需要添加定时器的请求…

BERT 详解

BERT简介 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是由 Google 在 2018 年提出的一种预训练语言模型。BERT 在自然语言处理&#xff08;NLP&#xff09;领域取得了重大突破&#xff0c;因为它能够有效地捕捉文本的上下文信息&am…

macos 14.0 Monoma 修改顶部菜单栏颜色

macos 14.0 设置暗色后顶部菜单栏还维持浅色&#xff0c;与整体不协调。 修改方式如下&#xff1a;

数据库-MySQL-Dynamic-Datasource源码解析

文章目录 前言一、简介二、整体流程三、核心解析四、总结 前言 多数据源的应用在日常项目中也是很常见的场景。 dynamic-datasource的功能&#xff0c;用起来的确很方便&#xff0c;只需要一个DS注解&#xff0c;加上一些简单的配置即可完成多数据源的切换。究竟是怎么做到的…

YB2503HV:高效率降压IC,助力电动车、太阳能设备等领域的能源转换

今天我要向大家介绍一款引人注目的产品—— YB2503HV 100V 3A SOP8内置MOS 高效率降压IC。这款单片集成芯片具备可设定输出电流的开关型降压恒压驱动器功能&#xff0c;可广泛应用于电动车、太阳能设备、电子电池充电等领域。让我们一起来看看它的特点和应用吧&#xff01; 首先…

架构-微服务架构

文章目录 前言一、系统架构演变1. 单体应用架构2. 垂直应用架构3. 分布式架构4. SOA 架构5. 微服务架构 二. 微服务架构介绍1. 微服务架构的常见问题2. 微服务架构的常见概念3. 微服务架构的常见解决方案4. 解决方案选型 三. Spring Cloud Alibaba介绍1. 主要功能2. 组件 前言 …

【一个简单的整数问题2——线段树】

题目 代码 下面的两个代码的区别在于modify的分类&#xff0c;modify最简单的分类方式是存在性分类&#xff0c;另一种类似某些query采用的三段式分类&#xff0c;详细见代码 存在性 #include <bits/stdc.h> using namespace std; using ll long long; const int N 1…

通用网络安全设备之【防火墙】

概念&#xff1a; 防火墙&#xff08;Firewall&#xff09;&#xff0c;也称防护墙&#xff0c;它是一种位于内部网络与外部网络之间的网络安全防护系统&#xff0c;是一种隔离技术&#xff0c;允许或是限制传输的数据通过。 基于 TCP/IP 协议&#xff0c;主要分为主机型防火…