Java 反射与动态代理:实践中的应用与陷阱

news2025/1/24 17:31:42

Java 反射与动态代理:实践中的应用与陷阱

在现代Java应用中,反射和动态代理提供了强大的灵活性,但它们也带来了性能和复杂度上的挑战。本文将深入探讨这些技术在实际项目中的应用,分析它们可能导致的陷阱,并提供详细的优化策略,尤其在大型系统或基于Spring的项目中,如何避免性能瓶颈和滥用。


一、Java反射的深入应用与陷阱

1. 反射的深入应用:

反射是Java中的一项强大特性,它允许我们在运行时操作类、对象以及方法。反射的灵活性使得它在很多框架和大型系统中都有着广泛的应用,特别是在需要 动态操作类的属性、方法动态加载类 时,反射发挥了重要作用。以下是几个典型的反射应用场景:

(1)框架设计与依赖注入:

反射是许多框架(如Spring、Hibernate)的核心技术之一,尤其在 依赖注入(DI)中,反射被用来动态地实例化类、注入依赖对象。

应用案例: 在Spring框架中,容器启动时会扫描配置的类(如通过@Component注解标注的类),通过反射解析类的构造函数、字段和方法,并通过依赖注入机制将对象的依赖关系建立起来。这个过程几乎完全依赖于反射。

public class MyService {
    private final DatabaseService databaseService;

    // 使用反射注入依赖
    public MyService(DatabaseService databaseService) {
        this.databaseService = databaseService;
    }
}

Spring在运行时会通过反射机制查找MyService的构造函数,然后根据配置动态地创建MyService对象,并通过反射将DatabaseService对象注入到它的构造函数中。

(2)插件架构与动态加载:

在构建插件系统时,反射可用于在运行时动态加载、实例化和调用插件类。插件通常遵循特定的接口或约定,反射让我们能够在不改变主程序代码的情况下动态加载插件。

应用案例: 假设我们需要设计一个通用的插件加载器,允许不同的插件在运行时加入系统:

public class PluginLoader {

    // 动态加载并实例化插件
    public Object loadPlugin(String className) throws Exception {
        Class<?> clazz = Class.forName(className);  // 通过反射加载插件类
        return clazz.getDeclaredConstructor().newInstance();  // 通过反射创建实例
    }
}

在这个例子中,PluginLoader使用反射动态加载指定的插件类,并创建插件实例。这样做使得系统能够灵活地加载和卸载插件,而不需要修改主程序。

(3)对象序列化与反序列化:

反射广泛用于对象的 序列化和反序列化,例如将对象转为JSON或XML格式时,反射能够动态读取类的字段并构建相应的数据结构。

应用案例: JSON框架(如Gson)使用反射动态读取类字段,然后将这些字段转化为JSON对象:

import com.google.gson.Gson;

public class User {
    private String name;
    private int age;

    // 构造函数和getter/setter省略
}

public class SerializationExample {
    public static void main(String[] args) {
        User user = new User("John", 30);
        Gson gson = new Gson();

        // 通过反射将对象转化为JSON
        String json = gson.toJson(user);
        System.out.println(json);  // 输出:{"name":"John","age":30}
    }
}

Gson框架通过反射获取User类的字段并构造JSON字符串。反射使得这一过程变得动态和通用,无需事先为每个类手动编写序列化代码。

2. 反射的陷阱与性能问题:

反射虽然非常灵活,但它也带来了潜在的 性能问题易错性。以下是常见的反射陷阱及示例,帮助开发者更好地理解如何避免这些问题。

(1)性能瓶颈:

反射操作相较于直接的代码调用,通常会有显著的性能开销。这是因为反射涉及 类的查找、类型匹配、方法调用 等操作,通常比直接调用方法更慢。

典型案例: 假设你在一个高并发的系统中,频繁地使用反射调用方法:

public class ReflectionPerformanceTest {
    public void testMethod() {
        System.out.println("Test method executed");
    }

    public static void main(String[] args) throws Exception {
        Method method = ReflectionPerformanceTest.class.getMethod("testMethod");

        long start = System.nanoTime();
        // 通过反射多次调用方法
        for (int i = 0; i < 100000; i++) {
            method.invoke(new ReflectionPerformanceTest());
        }
        long end = System.nanoTime();

        System.out.println("Time taken: " + (end - start) + " nanoseconds");
    }
}

分析: 在这个例子中,我们使用反射在循环中多次调用testMethod。反射调用的开销每次都增加,尤其是在高并发系统中,这种频繁的反射调用会带来显著的性能瓶颈。

性能影响: 反射会增加:

  • 方法查找:每次调用都需要通过Method.invoke()查找并验证方法。
  • 类型转换:反射过程中需要进行类型转换,增加了CPU负担。

在每秒钟需要执行大量请求的系统中,这种性能损耗是显而易见的。

(2)类型安全问题:

反射绕过了编译时的类型检查,因此容易引发类型不匹配的问题,导致 运行时错误

典型案例: 假设我们在使用反射时没有正确处理方法的参数类型:

public class ReflectionTypeSafety {
    public void displayMessage(String message) {
        System.out.println(message);
    }

    public static void main(String[] args) throws Exception {
        Method method = ReflectionTypeSafety.class.getMethod("displayMessage", Object.class);
        // 错误:将一个Object类型的参数传递给一个期望String类型的参数
        method.invoke(new ReflectionTypeSafety(), 123);
    }
}

错误分析: 虽然我们通过反射将displayMessage方法的参数声明为Object.class,但该方法期望的参数类型是String。反射绕过了编译时的类型检查,导致在运行时传入了不匹配的类型,抛出了ClassCastException

陷阱与风险:

  • 反射绕过了Java的 类型安全机制,使得程序更容易引入潜在的类型错误。
  • 运行时错误难以追踪,尤其是当反射调用链较长时,错误更难排查。
(3)反射的代码复杂性:

反射虽然能提供灵活性,但它也使代码变得更加复杂,尤其是在大量依赖反射的系统中,代码的可读性和可维护性都会降低。

典型案例: 在没有反射时,我们通常通过直接调用对象的方法来进行操作。然而,使用反射时,我们需要手动查找方法并设置字段,这可能导致代码冗长且难以理解。

public class ReflectionComplexity {
    public void displayMessage(String message) {
        System.out.println(message);
    }

    public static void main(String[] args) throws Exception {
        // 传统的直接调用
        ReflectionComplexity obj = new ReflectionComplexity();
        obj.displayMessage("Direct call");

        // 使用反射进行调用
        Class<?> clazz = ReflectionComplexity.class;
        Method method = clazz.getDeclaredMethod("displayMessage", String.class);
        method.setAccessible(true);  // 需要额外的步骤来处理私有方法
        method.invoke(obj, "Reflection call");
    }
}

复杂性分析:

  • 通过反射调用displayMessage方法需要更多的步骤(获取Class对象、查找方法、设置方法可访问性等)。
  • 代码冗长且难以维护,特别是在涉及多个类和复杂的逻辑时。
优化策略:

为了缓解反射带来的性能瓶颈和代码复杂性,以下是几条优化策略:

  1. 缓存反射结果:

    • 对于反射调用中频繁使用的MethodField等结果,可以进行缓存,以避免重复的查找操作。
    public class ReflectionCache {
        private static Map<String, Method> methodCache = new HashMap<>();
    
        public static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
            String key = clazz.getName() + "." + methodName;
            if (!methodCache.containsKey(key)) {
                Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
                methodCache.put(key, method);
            }
            return methodCache.get(key);
        }
    }
    
  2. 减少反射调用:

    • 在性能关键路径中尽量减少反射调用,避免在循环或高频次调用中使用反射。
    • 可以考虑在应用启动时预先加载并缓存反射结果,尽量避免在每次请求中都进行反射操作。
  3. 考虑替代方案:

    • 如果可能,使用更加 类型安全 的方案,如 接口工厂模式 来替代反射,减少反射的使用范围。

二、动态代理的深入应用与陷阱

1. 动态代理的深入应用

动态代理是Java中一种非常灵活的机制,它允许程序在运行时为接口创建代理对象,并通过代理对象拦截方法调用,注入额外的逻辑。动态代理在框架开发中非常重要,尤其是在实现 AOP(面向切面编程)和 远程方法调用(RPC)等场景中,起到了核心作用。

以下是几种典型的动态代理应用场景和相关实现:


(1)AOP(面向切面编程)中的应用

动态代理是实现AOP的基础,尤其在 Spring AOP 中,动态代理用于拦截方法调用并执行额外逻辑,比如日志记录、事务管理等。

应用案例: 以下是一个简单的AOP示例,展示如何使用JDK动态代理实现方法拦截:

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

// 定义一个接口
interface Service {
    void performTask();
}

// 接口实现类
class ServiceImpl implements Service {
    @Override
    public void performTask() {
        System.out.println("执行任务...");
    }
}

// 动态代理类
class LoggingProxyHandler implements InvocationHandler {
    private final Object target;

    public LoggingProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志记录: 调用方法 " + method.getName());
        Object result = method.invoke(target, args); // 调用实际方法
        System.out.println("日志记录: 方法 " + method.getName() + " 执行完成");
        return result;
    }
}

public class AopExample {
    public static void main(String[] args) {
        // 创建目标对象
        Service target = new ServiceImpl();

        // 创建代理对象
        Service proxy = (Service) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new LoggingProxyHandler(target)
        );

        // 调用代理方法
        proxy.performTask();
    }
}

输出:

日志记录: 调用方法 performTask
执行任务...
日志记录: 方法 performTask 执行完成

分析:

  • 通过Proxy.newProxyInstance()创建了一个动态代理对象。
  • 动态代理拦截了对目标对象的所有方法调用,并在调用前后添加日志逻辑。
  • 这种机制是Spring AOP的基础,用于实现类似事务管理和性能监控的功能。

(2)RPC框架中的应用

在远程方法调用(RPC)场景中,动态代理用于隐藏远程调用的复杂性,开发者只需调用本地代理方法,而代理会在内部处理远程通信逻辑。

应用案例: 以下是一个简化的RPC动态代理实现:

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

// 定义一个远程服务接口
interface RemoteService {
    String fetchData(String param);
}

// 动态代理实现
class RpcProxyHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 模拟发送远程请求
        System.out.println("发送远程请求: 方法名 " + method.getName() + ",参数 " + args[0]);
        // 模拟远程返回结果
        return "远程数据: " + args[0];
    }
}

public class RpcExample {
    public static void main(String[] args) {
        // 创建远程服务代理
        RemoteService proxy = (RemoteService) Proxy.newProxyInstance(
                RpcExample.class.getClassLoader(),
                new Class[]{RemoteService.class},
                new RpcProxyHandler()
        );

        // 调用代理方法
        String result = proxy.fetchData("test");
        System.out.println(result);
    }
}

输出:

发送远程请求: 方法名 fetchData,参数 test
远程数据: test

分析:

  • 动态代理隐藏了远程通信的细节,开发者只需像调用普通方法一样调用代理对象。
  • RPC框架(如Dubbo、gRPC)通过动态代理实现接口代理,大大简化了远程方法调用的复杂度。

(3)方法拦截与监控

动态代理可以用于性能监控,统计方法调用的时间或参数。例如,在某些监控系统中,我们需要记录所有服务方法的执行耗时。

应用案例:

class MonitoringHandler implements InvocationHandler {
    private final Object target;

    public MonitoringHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long endTime = System.currentTimeMillis();
        System.out.println("方法 " + method.getName() + " 耗时: " + (endTime - startTime) + " ms");
        return result;
    }
}

通过这种方法拦截器,我们可以动态代理任何服务类并统计其方法的执行耗时。


2. 动态代理的陷阱与性能问题

虽然动态代理功能强大,但在实际应用中存在以下陷阱和问题:


(1)性能开销:

动态代理的性能开销主要来自以下两部分:

  1. 反射调用开销: JDK动态代理通过反射调用目标方法(Method.invoke()),相比直接调用方法会慢很多。
  2. 代理链增加复杂性: 如果多个代理对象链式嵌套,每次方法调用都会经过多层代理,进一步增加调用的耗时。

性能测试: 以下代码对比了动态代理和直接调用的性能:

public class ProxyPerformanceTest {
    public static void main(String[] args) throws Exception {
        // 目标对象
        Service target = new ServiceImpl();

        // 创建动态代理
        Service proxy = (Service) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new LoggingProxyHandler(target)
        );

        // 测试直接调用
        long start = System.nanoTime();
        for (int i = 0; i < 100000; i++) {
            target.performTask();
        }
        long end = System.nanoTime();
        System.out.println("直接调用耗时: " + (end - start) + " 纳秒");

        // 测试代理调用
        start = System.nanoTime();
        for (int i = 0; i < 100000; i++) {
            proxy.performTask();
        }
        end = System.nanoTime();
        System.out.println("代理调用耗时: " + (end - start) + " 纳秒");
    }
}

结果分析:

  • 直接调用耗时显著低于动态代理。
  • 在高并发或频繁调用场景中,动态代理的性能损耗会被放大。

(2)代理类的复杂性和可维护性

当多个动态代理层嵌套时,代码逻辑变得复杂且难以调试。例如,多个代理链式调用可能导致难以追踪的调用链问题,尤其是在代理方法之间传递参数或处理异常时。


(3)代理对象的类型限制

JDK动态代理要求目标类必须实现接口。如果目标类没有接口,JDK动态代理无法直接使用,必须借助CGLIB等工具来实现。


3. 动态代理的优化策略

为了降低动态代理的性能开销和复杂性,可以采取以下优化策略:


(1)使用CGLIB代理优化性能

JDK动态代理使用反射调用方法,而CGLIB通过生成目标类的子类,直接调用方法(绕过反射),性能更高。

优化示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class CglibProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ServiceImpl.class); // 设置父类
        enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
            System.out.println("日志记录: 调用方法 " + method.getName());
            Object result = proxy.invokeSuper(obj, args1); // 调用父类方法
            System.out.println("日志记录: 方法 " + method.getName() + " 执行完成");
            return result;
        });

        ServiceImpl proxy = (ServiceImpl) enhancer.create();
        proxy.performTask();
    }
}

(2)减少代理层级

尽量减少代理链层级,可以将多个代理逻辑合并为一个代理类,避免链式调用带来的性能损耗。


(3)在非高频调用中使用代理

动态代理的性能损耗在高频调用场景中较为显著,尽量将代理逻辑应用在初始化阶段或非高频的调用路径中。


三、Spring AOP中的反射与动态代理应用

1. Spring AOP 的核心原理

Spring AOP 是 面向切面编程(Aspect-Oriented Programming) 的一种实现,用于在不修改原始代码的情况下动态增强目标对象的功能。例如,在一个方法执行前后添加日志、权限验证、事务管理等。

Spring AOP 的实现主要基于以下两种动态代理方式:

  1. JDK动态代理: 代理目标对象的接口(要求目标对象实现接口)。
  2. CGLIB代理: 如果目标对象没有实现接口,则使用 CGLIB 生成目标类的子类作为代理。

2. Spring AOP 的反射与动态代理结合

在 Spring AOP 中,动态代理与反射结合,用于拦截目标方法的调用,并注入切面逻辑。以下是具体过程:

  1. 代理对象的生成:
    • 如果目标对象实现了接口,Spring 使用 JDK 动态代理生成代理对象。
    • 如果目标对象没有实现接口,则使用 CGLIB 动态代理。
  2. 拦截方法调用:
    • 代理对象会拦截对目标方法的调用,并通过反射调用切面逻辑和目标方法。
  3. 切面增强:
    • 通过 @Around@Before 等注解定义的切面逻辑会在目标方法执行前后注入。

示例: 以下是一个基于 Spring AOP 的日志记录案例:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

// 定义切面类
@Aspect
@Component
public class LoggingAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object logMethodCall(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("日志记录: 方法 " + methodName + " 开始执行");
        
        // 调用目标方法
        Object result = joinPoint.proceed();

        System.out.println("日志记录: 方法 " + methodName + " 执行完成");
        return result;
    }
}

// 目标类
@Component
public class UserService {
    public void registerUser(String username) {
        System.out.println("注册用户: " + username);
    }
}

// 测试类
@SpringBootApplication
public class AopTestApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(AopTestApplication.class, args);
        UserService userService = context.getBean(UserService.class);
        userService.registerUser("Alice");
    }
}

输出:

日志记录: 方法 registerUser 开始执行
注册用户: Alice
日志记录: 方法 registerUser 执行完成

分析:

  • Spring AOP 拦截了对 registerUser 方法的调用,在方法执行前后注入了日志逻辑。
  • Spring 通过反射调用目标方法(joinPoint.proceed())以及读取方法的参数和返回值。

3. Spring AOP 中的性能问题

Spring AOP 的灵活性和强大功能是以 动态代理反射调用 为代价的,因此在性能敏感的场景中,可能会出现以下问题:

(1)反射调用的开销

Spring AOP 的 joinPoint.proceed() 本质上是通过反射调用目标方法,反射调用的性能开销比直接调用方法更高,尤其是在高并发或频繁调用的场景中。

(2)代理对象的创建开销
  • JDK 动态代理在创建代理对象时会生成一个实现目标接口的代理类,该过程需要一定的时间和内存。
  • CGLIB 代理需要生成目标类的子类,并通过字节码操作增强功能,其创建成本更高。
(3)嵌套代理带来的性能问题

当一个目标对象被多个切面增强时,会生成多层代理对象。每次方法调用都需要经过多层代理链,这种嵌套会显著增加调用的延迟。

(4)无法代理私有方法

Spring AOP 无法拦截目标对象的私有方法,因为动态代理和 CGLIB 都需要在运行时访问方法签名,而私有方法的访问权限受限。

性能分析测试: 假设我们对一个方法使用多个切面增强,并测试其性能:

@Aspect
@Component
public class TimingAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println("方法 " + joinPoint.getSignature().getName() + " 耗时: " + (end - start) + " ms");
        return result;
    }
}

如果此方法被频繁调用,每次的反射和代理链开销都会累积,对系统性能造成影响。


4. Spring AOP 的优化策略

为了解决 Spring AOP 中的性能问题,可以采取以下优化措施:

(1)合理选择代理类型
  • 优先使用 JDK 动态代理:
    • 如果目标类实现了接口,尽量使用 JDK 动态代理,因为它的代理对象创建成本比 CGLIB 低。
    • Spring 默认会优先使用 JDK 动态代理。
  • 必要时使用 CGLIB:
    • 如果目标类没有实现接口,则只能使用 CGLIB 代理,但需要注意 CGLIB 的性能开销。

强制启用 JDK 动态代理:

application.properties 中设置:

spring.aop.proxy-target-class=false
(2)减少不必要的切面
  • 确保切面只应用于必要的业务逻辑,而不是每个方法。例如,事务管理切面只需要应用在数据库操作相关的服务中。
  • 对切面范围进行限制,比如只拦截特定的包或类。

优化示例: 通过正则表达式限制切面作用范围:

@Around("execution(* com.example.service.UserService.*(..))")
public Object logUserServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed();
}
(3)减少代理链层级

将多个切面的逻辑合并到一个切面中,避免多层代理链增加调用延迟。

示例: 将日志记录和性能监控合并到一个切面中:

@Aspect
@Component
public class CombinedAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object combinedLogic(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("日志记录: 方法 " + joinPoint.getSignature().getName() + " 开始");
        
        Object result = joinPoint.proceed();

        long end = System.currentTimeMillis();
        System.out.println("日志记录: 方法 " + joinPoint.getSignature().getName() + " 执行完成");
        System.out.println("方法耗时: " + (end - start) + " ms");
        return result;
    }
}
(4)避免过度使用 AOP
  • 如果某些增强逻辑不需要动态性(例如日志),可以通过 显式调用方法工具类 替代 AOP,减少动态代理的使用。

5. CGLIB 代理的局限性与优化
局限性:
  • CGLIB 无法代理 final 类和 final 方法。
  • CGLIB 创建代理对象的成本较高,在高并发场景下可能会影响性能。
优化建议:
  • 避免在 CGLIB 代理的目标类中使用 final 修饰的方法或类。
  • 在需要频繁创建代理对象的场景中,提前加载代理对象并缓存,避免重复创建。

示例: 使用缓存保存代理对象:

public class ProxyCache {
    private static final Map<Class<?>, Object> proxyCache = new HashMap<>();

    public static <T> T getProxy(Class<T> clazz, MethodInterceptor interceptor) {
        return (T) proxyCache.computeIfAbsent(clazz, key -> {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(key);
            enhancer.setCallback(interceptor);
            return enhancer.create();
        });
    }
}

结论

Spring AOP 的核心在于动态代理与反射的结合,它为开发者提供了灵活的切面编程能力。然而,反射调用和代理对象创建带来的性能开销在高并发场景中会显著放大。通过合理选择代理类型、限制切面范围、合并切面逻辑以及缓存代理对象,可以有效优化 Spring AOP 的性能。理解 AOP 的工作机制和限制,能帮助我们更好地应用这项技术,同时避免滥用带来的隐性问题。


总结

反射与动态代理在Java中提供了强大的灵活性,但在实际应用中,滥用这些特性会导致性能瓶颈和复杂性。通过合理的缓存机制、减少不必要的代理层级、选择合适的代理框架和策略,我们可以有效地减少这些技术带来的负担。在大规模的企业级系统中,尤其是在基于Spring的应用中,合理地使用反射和动态代理,避免陷入性能瓶颈,是开发高效且可维护系统的关键。

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

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

相关文章

UE求职Demo开发日志#8 强化前置条件完善,给物品加图标

1 强化前置条件完善 StrengthManager里实现一个Check前置的函数 bool CheckPreAllIsActive(int index)&#xff0c;所有的前置都已经激活就返回true&#xff0c;否则返回false 之后在强化的时候加入条件检查&#xff1a; 1.所有前置技能全部激活 2.本身没有强化过 最后测…

pinctrl子系统

目录 一、PinCtrl子系统的定义 二、明确PinCtrl子系统和我们编写驱动的关系 三、pinctrl_desc结构体引入 四、PinCtrl子系统驱动实现分析 1.芯片厂家是如何实现PinCtrl子系统的 2.linux在什么位置设置的引脚复用和电气属性 2.1 really_probe的主要功能 2.2 really_prob…

行政纠错——pycorrector学习

pycorrector是一个开源中文文本纠错工具&#xff0c;它支持对中文文本进行音似、形似和语法错误的纠正。此工具是使用Python3进行开发的&#xff0c;并整合了Kenlm、ConvSeq2Seq、BERT、MacBERT、ELECTRA、ERNIE、Transformer等多种模型来实现文本纠错功能。pycorrector官方仓库…

深入MapReduce——计算模型设计

引入 通过引入篇&#xff0c;我们可以总结&#xff0c;MapReduce针对海量数据计算核心痛点的解法如下&#xff1a; 统一编程模型&#xff0c;降低用户使用门槛分而治之&#xff0c;利用了并行处理提高计算效率移动计算&#xff0c;减少硬件瓶颈的限制 优秀的设计&#xff0c…

React+Cesium基础教程(001):创建基于React的Cesium项目及对Cesium进行基本配置

文章目录 01-基于react的cesium项目创建基于React的Cesium项目Cesium基本配置设置默认启动视角完整项目下载地址01-基于react的cesium项目 创建基于React的Cesium项目 创建react项目: create-react-app react-cesium-basic安装[cesium1.93.0]版本: npm install cesium@1.…

Vue2:使用sortablejs实现el-table中行拖拽调整顺序

如图,实现拖拽表格中的行来调整行顺序,但是其中的编号仍然是1、2、3、4的顺序,不跟着变化。 实现如下: 一、导入sortablejs import Sortable from "sortablejs";export default { components: {Sortable},data() {return {//数据中的id很重要,拖拽行重新排序…

Kingbase数据库体系结构和日常运维监控

1. 数据库架构 1.1. 内存 1.1.1. Share Memory 共享内存是服务器服务器为数据库缓存和事务日志缓存预留的内存缓存空间&#xff0c;其中最重要的组成部分是Shared Buffer和WAL Buffer&#xff1a; 1. Shared Buffer&#xff1a; 减少磁盘IO 2. WAL Buffer&#xff1a; 用…

【Prometheus】Prometheus如何监控Haproxy

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【0x0012】HCI_Delete_Stored_Link_Key命令详解

目录 一、命令参数 二、命令格式及参数 2.1. HCI_Delete_Stored_Link_Key 命令格式 2.2. BD_ADDR 2.3. Delete_All 三、生成事件及参数 3.1. HCI_Command_Complete事件 3.2. Status 3.3. Num_Keys_Deleted 四、命令执行流程 4.1. 命令发送阶段 4.2. 控制器处理阶段…

github汉化

本文主要讲述了github如何汉化的方法。 目录 问题描述汉化步骤1.打开github&#xff0c;搜索github-chinese2.打开项目&#xff0c;打开README.md3.下载安装脚本管理器3.1 在README.md中往下滑动&#xff0c;找到浏览器与脚本管理器3.2 选择浏览器对应的脚本管理器3.2.1 点击去…

机器学习-K近邻算法

文章目录 一. 数据集介绍Iris plants dataset 二. 代码三. k值的选择 一. 数据集介绍 鸢尾花数据集 鸢尾花Iris Dataset数据集是机器学习领域经典数据集&#xff0c;鸢尾花数据集包含了150条鸢尾花信息&#xff0c;每50条取自三个鸢尾花中之一&#xff1a;Versicolour、Setosa…

C++函数——fill

在C中&#xff0c;std::fill 是标准库提供的一个算法适用于几乎所有类型的容器&#xff0c;只要这些容器支持迭代器操作。具体来说&#xff0c;std::fill 的适用性取决于容器是否提供了满足其要求的迭代器类型&#xff0c;用于将指定范围内的所有元素设置为某个特定值。它是一个…

如何打造高效同城O2O平台?外卖跑腿系统源码选型与开发指南

打造一个高效的同城O2O平台&#xff0c;选择合适的外卖跑腿系统源码并制定科学的开发方案至关重要。本篇文章&#xff0c;笔者将从源码选型、核心功能、开发架构及运营策略等方面&#xff0c;全面解析O2O平台的搭建思路。 一、外卖跑腿系统源码的选型要点 在搭建O2O平台时&…

Flutter_学习记录_基本组件的使用记录

1.TextWidge的常用属性 1.1TextAlign: 文本对齐属性 常用的样式有&#xff1a; TextAlign.center 居中TextAlign.left 左对齐TextAlign.right 有对齐 使用案例&#xff1a; body: Center(child: Text(开启 TextWidget 的旅程吧&#xff0c;珠珠, 开启 TextWidget 的旅程吧&a…

Qt实践:一个简单的丝滑侧滑栏实现

Qt实践&#xff1a;一个简单的丝滑侧滑栏实现 笔者前段时间突然看到了侧滑栏&#xff0c;觉得这个抽屉式的侧滑栏非常的有趣&#xff0c;打算这里首先尝试实现一个简单的丝滑侧滑栏。 首先是上效果图 &#xff08;C&#xff0c;GIF帧率砍到毛都不剩了&#xff09; QProperty…

10. SpringCloud Alibaba Sentinel 规则持久化部署详细剖析

10. SpringCloud Alibaba Sentinel 规则持久化部署详细剖析 文章目录 10. SpringCloud Alibaba Sentinel 规则持久化部署详细剖析1. 规则持久化1.1 Nacos Server 配置中心-规则持久化实例 2. 最后&#xff1a; 1. 规则持久化 规则没有持久化的问题 如果 sentinel 流控规则没有…

SpringCloud微服务Gateway网关简单集成Sentinel

Sentinel是阿里巴巴开源的一款面向分布式服务架构的轻量级流量控制、熔断降级组件。Sentinel以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度来帮助保护服务的稳定性。 官方文档&#xff1a;https://sentinelguard.io/zh-cn/docs/introduction.html …

正则表达式以及Qt中的使用

目录 一、正则表达式 1、基本匹配&#xff1a; 2、元字符&#xff1a; 2.1 .运算符&#xff1a; 2.2 字符集&#xff1a; 2.3 重复次数&#xff1a; 2.4 量词{} 2.5 特征标群() 2.6 或运算符 2.7 \反斜线转码特殊字符 2.8 锚点 3、简写字符 4、零宽度断言 4.1 正…

【STM32HAL-----GPIO】

1. 什么是GPIO&#xff1f;&#xff08;了解&#xff09; 2. STM32 GPIO简介 2.1. GPIO特点 2.2. GPIO电气特性 2.3. GPIO引脚分布图 IO引脚分布特点&#xff1a;按组存在、组数视芯片而定、每组最多16个IO引脚。 3. IO端口基本结构介绍 4. GPIO八种工作模式 4.1. 输入浮空 特…

亲测有效!解决PyCharm下PyEMD安装报错 ModuleNotFoundError: No module named ‘PyEMD‘

解决PyCharm下PyEMD安装报错 PyEMD安装报错解决方案 PyEMD安装报错 PyCharm下通过右键自动安装PyEMD后运行报错ModuleNotFoundError: No module named ‘PyEMD’ 解决方案 通过PyCharm IDE python package搜索EMD-signal&#xff0c;选择版本后点击“install”执行安装