【Spring Clound】Feign底层原理分析-自动装载动态代理

news2025/1/19 16:33:19

文章目录

  • 前言
  • 1、什么是Feign?
  • 2、为什么要使用Feign?
  • 3、Feign依赖注入原理
    • 3.1、Feign自动装载
    • 3.2、FeignClientFactoryBean#getObject
  • 4、总结

前言

是一个HTTP请求调用轻量级框架,可以以Java接口注解的方式调用HTTP请求,而不用像Java中通过封装HTTP请求报文的方式直接调用,下面详细介绍。

1、什么是Feign?

这里套用Feign官方Github上的介绍:“Feign是一个灵感来自于Retrofit、JAXRS-2.0、WebSocket的Java Http客户端,Feign的主要目标是降低大家使用Http API的复杂性”。

其实,Feign底层依赖于Java的动态代理机制,对原生Java Socket或者Apache HttpClient进行封装,实现了基于Http协议的远程过程调用

当然,Feign还在此基础上实现了负载均衡、熔断等机制。

2、为什么要使用Feign?

  • 声明式Http Client相对于编程式Http Client代码逻辑更加简洁,不需要处理复杂的编码请求和响应,只需要像调用本地方法即可,提高编码效率
  • 集中管理Http请求方法,代码边界更加清晰
  • 更好的集成负载均衡、熔断降级等功能

3、Feign依赖注入原理

使用过Feign的同学都知道,@EnableFeignClients注解是开启Fiegn功能的关键,我们通常会在该注解中添加FeignClient的所在包,以便Spring容器能够扫描到所有的FeignClient,并进行托管。后面我们便可以使用@Autowired注解自动导入了。

@SpringBootApplication
@EnableFeignClients(basePackages = {"com.**.feign"})
public class Application {}

该注解样式也是很多第三方包集成Springboot所使用的套路:一般都是开启该注解后,Springboot便可以自动装载第三方包所指定的Class,我们便可以直接使用第三方包所提供的功能,非常方便。

接下来不会详细介绍自动装载的部分,而是直接给出自动装载的主脉络,看看Spring容器到底装载了什么bean。

3.1、Feign自动装载

首先进入@EnableFeignClients源码中,查看该注解导入了什么Registrar注册器,这个注册器便是自动装载的关键。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {...}

从源码中可以看出,@EnableFeignClients 注解导入的是自定义的FeignClientsRegistrar类。

这种类型的注册器一般会继承Spring中的ImportBeanDefinitionRegistrar接口,并在registerBeanDefinitions实现方法中向Spring容器注册一些bean,以达到自动注入第三方功能的目的。

// [1] 继承ResourceLoaderAware和EnvironmentAware
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // [2] 注册默认的Feign配置
        registerDefaultConfiguration(metadata, registry);
        // [3] 注册所有定义的FeignClient
        registerFeignClients(metadata, registry);
    }
}
  • [1] 从类的定义中,我们可以发现还实现了ResourceLoaderAware、EnvironmentAware两个Spring钩子接口,那么该注册类必然持有资源加载器和Spring的环境变量等信息,这个不过多叙述。
  • [2] 该方法会从@EnableFeignClients注解中提取defaultConfiguration这个key和对应的value,并把它当作默认的Feign配置注册到Spring容器中。如果没有该key,则不做任何处理。
  • [3] registerFeignClients方法会扫描@EnableFeignClients注解的basePackages,注册所有的FeignClient,下面详细介绍。
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
    Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    // [1] 获取clients属性
    final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        // [2] 如果clients属性为null,则获取basePackages属性,扫描其中的所有client
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
        Set<String> basePackages = getBasePackages(metadata);
        for (String basePackage : basePackages) {
            candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
        }
    } else {
        // [3] 如果clients属性不为null,则直接注入
        for (Class<?> clazz : clients) {
            candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
        }
    }
    // [4] 遍历所有的clients,将其封装为BeanDefinition注册进Spring容器中
    for (BeanDefinition candidateComponent : candidateComponents) {
        if (candidateComponent instanceof AnnotatedBeanDefinition) {
            // verify annotated class is an interface
            AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
            AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
            Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
    
            Map<String, Object> attributes = annotationMetadata
                .getAnnotationAttributes(FeignClient.class.getCanonicalName());
    
            String name = getClientName(attributes);
            registerClientConfiguration(registry, name, attributes.get("configuration"));
    
            registerFeignClient(registry, annotationMetadata, attributes);
        }
    }
}

registerFeignClients方法的代码稍微有点多,但主要思路就是解析@EnableFeignClients注解,根据解析信息获取FeignClients,然后包装成BeanDefinition注册进Spring容器中。具体分为以下几步:

  • [1] 首先解析@EnableFeignClients注解中的clients信息,如果存在,说明开发人员直接指定了FeignClient的全路径,因此只要加载这些全路径的class即可。如果未指定,则通过扫包的方式加载。
  • [2] 如果clients属性为null,则创建一个扫描器Scanner,并指定要扫描的类必须有FeignClient注解,然后通过getBasePackages()方法从@EnableFeignClients注解中获取basePackages信息,最后遍历所有待扫描的包,将扫描到的FeignClient类加入candidateComponents中,待后续加载进容器。
  • [3] 如果clients属性不为null,上面也说了,会直接注入
  • [4] 到了这里,所有的FeignClient都被扫描到并且封装成BeanDefinition,接下来会遍历这些-BeanDefinition,然后在Assert.isTrue()方法中判断这些BeanDefinition是否为接口,因为@FeignClient注解只能使用在接口上。校验完后我们应该关注registerFeignClient()这个真正注册FeignClient的方法。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
    Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    Class clazz = ClassUtils.resolveClassName(className, null);
    ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
        ? (ConfigurableBeanFactory) registry : null;
    String contextId = getContextId(beanFactory, attributes);
    String name = getName(attributes);
    FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
    factoryBean.setBeanFactory(beanFactory);
    factoryBean.setName(name);
    factoryBean.setContextId(contextId);
    factoryBean.setType(clazz);
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
        factoryBean.setUrl(getUrl(beanFactory, attributes));
        factoryBean.setPath(getPath(beanFactory, attributes));
        factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
        Object fallback = attributes.get("fallback");
        if (fallback != null) {
          factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
              : ClassUtils.resolveClassName(fallback.toString(), null));
        }
        Object fallbackFactory = attributes.get("fallbackFactory");
        if (fallbackFactory != null) {
          factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
              : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
        }
        return factoryBean.getObject();
    });
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    definition.setLazyInit(true);
    validate(attributes);
  
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
    beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
  
    // has a default, won't be null
    boolean primary = (Boolean) attributes.get("primary");
  
    beanDefinition.setPrimary(primary);
  
    String[] qualifiers = getQualifiers(attributes);
    if (ObjectUtils.isEmpty(qualifiers)) {
      qualifiers = new String[] { contextId + "FeignClient" };
    }
  
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

registerFeignClient()是对单个FeignClient注册的方法。这个方法乍一看很长,但其实主线非常清晰。

就是将扫描到的BeanDefinition中的元信息提取出来,然后构造成一个Feign自定义的FactoryBean,即FeignClientFactoryBean,后面我们每次获取容器中的FeignClient时,就会通过该FactoryBean的getObject()方法中获取(这个涉及到了Spring容器中普通bean和FactoryBean的区别,大家可以自行去了解下)。

方法中的其它部分都是为上面所说的逻辑服务,包括FactoryBean的构造,BeanDefinition注入到容器中等过程。我们不必太过关心,只要抓住重点即可。

讲到这里,其实自动装载过程已经完成了,容器中已经包含了自定义的FeignClientFactoryBean。这里用一张流程图总结下自动装载的全过程。

在这里插入图片描述

但是我们暂时还不知道自动装载的FeignClientFactoryBean到底做了什么。下面我们就深入去了解。

3.2、FeignClientFactoryBean#getObject

FeignClientFactoryBean的源码如下:

public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
    @Override
    public Object getObject() {
        // [1] 真正的获取对象的方法委托给getTarget()方法了
        return getTarget();
    }
    <T> T getTarget() {
        // 获取Feign的上下文
        FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
            : applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);
        if (!StringUtils.hasText(url)) {
            // [2] 如果url没有定义,则进入到该判断中创建对象,该判断中创建的对象具有负载均衡功能
            if (!name.startsWith("http")) {
                url = "http://" + name;
            }
            else {
                url = name;
            }
            url += cleanPath();
            return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
        }
        if (StringUtils.hasText(url) && !url.startsWith("http")) {
            url = "http://" + url;
        }
        // [3] 如果url定义了,说明用户指定了某台机器,也就没有必要进行负载均衡了,则从下面的方法创建对象
        String url = this.url + cleanPath();
        // 可以发现下面的执行逻辑和loadBalance()非常像,只是多了两个if判断,这两个if判断就是移除负载均衡的关键
        Client client = getOptional(context, Client.class);
        if (client != null) {
            if (client instanceof FeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
            }
            if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
            }
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
    }
}
// 创建负载均衡的client
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        Targeter targeter = get(context, Targeter.class);
        return targeter.target(this, builder, context, target);
    }
  
    throw new IllegalStateException(
        "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}

getObject()会根据用户定义的FeignClient是否定义url属性决定是否返回具有负载均衡属性的对象。具体过程如下:

  • [1] 重写的方法委托给getTarget()执行了
  • [2] 如果没有定义url,则直接通过loadBalance()方法创建代理对象。此时会先从容器中拿到Client对象,然后使用targeter.target()方法创建Client的代理对象。注意,我们最终使用的FeignClient是Client的动态代理对象,而Client对象是真正执行http请求的对象。Client一般是httpClient或者是Feign自定义的具有LoadBalance功能的LoadBalancerClient。前者很好理解,可以直接认为是Apache HttpClient;后者是Feign在Apache HttpClient的基础上封装了Spring Cloud Loadbalancer一系列对象,而Apache HttpClient作为被封装的delegate,在delegate真正执行http请求时同时进行Loadbalancer的负载均衡逻辑。
  • [3] 如果url已经定义了,说明用户指定了具体某台机器,此时已经没有必要进行负载均衡了(当然,如果配置的是域名,可能会由下游ng或者网关层进行负载均衡,这里说的是Feign没有必要负载均衡)。为了移除负载均衡的功能,这里比loadBalance()方法多了client的判断,如果client是FeignBlockingLoadBalancerClient或者RetryableFeignBlockingLoadBalancerClient,会直接代理这两个对象中的delegate对象,即直接代理Apache HttpClient,这样就能移除其中的负载均衡功能了。

从上面的源码中可知,无论是否需要负载均衡,都会通过targeter.target()方法创建动态代理对象。我们这里跳过中间不重要的环节,给出targeter.target()不太重要的调用栈,大家可以自行查看:Targeter.target()→DefaultTargeter.target()→Feign.Builder.target()→Feign.newInstance()→ReflectiveFeign.newInstance()。

接下来我们来到了ReflectiveFeign.newInstance()这个重要的方法:

@Override
public <T> T newInstance(Target<T> target) {
    // [1] nameToHandler 里面基本上是SynchronousMethodHandler,主要用于处理用户自定义的方法
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    // [2] DefaultMethodHandler用于处理接口中default方法
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
  
    // 遍历接口中的所有方法
    for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
            // [3] 如果是Object中的方法,直接跳过
            continue;
        } else if (Util.isDefault(method)) {
            // [4] 如果是default方法,则创建DefaultMethodHandler处理
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
        } else {
            // [5] 其它的都是用户自定义的方法了,此时从nameToHandler中拿出SynchronousMethodHandler进行映射
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }
    // [6] 这个就是动态代理的关键了,代理逻辑都在该handler中了
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 创建代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);
    
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
    }
    // 返回代理对象
    return proxy;  
}

从newInstance中我们看到了熟悉的proxy和InvocationHandler,这也就说明了Feign的底层还是依赖了JDK的动态代理:

  • [1] 这里会通过targetToHandlersByName.apply()方法创建configKey→SynchronousMethodHandler的映射
  • [2] defaultMethodHandlers用于存储处理用户定义的FeignClient接口中的default方法的handler
  • [3] 如果是Object方法,这里直接跳过了,因为会在后面的InvocationHandler中处理Object方法的代理逻辑
  • [4] 如果是接口中的default方法,则创建DefaultMethodHandler并添加进defaultMethodHandlers列表和methodToHandler 映射中
  • [5] 创建method→SynchronousMethodHandler的映射
  • [6] 创建InvocationHandler 核心代理对象,代理逻辑都封装在该对象中。注意,这里传递了methodToHandler(method→MethodHandler)这个映射,MethodHandler可能是DefaultMethodHandler(处理default方法)或者SynchronousMethodHandler(处理用户定义的远程调用方法)。代理过程中,会根据方法名称dispatch到这个映射中对应的MethodHandler进行处理。

后面就是创建代理对象并返回了。

下面我们来看核心代理逻辑究竟做了什么,InvocationHandler实现类为FeignInvocationHandler,是ReflectiveFeign的静态内部类。

static class FeignInvocationHandler implements InvocationHandler {
    private final Target target;
    private final Map<Method, MethodHandler> dispatch;
    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
        this.target = checkNotNull(target, "target");
        // dispatch就是我们上文提到的methodToHandler
        this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 这下面都是判断是否为Object中的方法
        if ("equals".equals(method.getName())) {
            try {
                Object otherHandler =
                    args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                return equals(otherHandler);
            } catch (IllegalArgumentException e) {
                return false;
            }
        } else if ("hashCode".equals(method.getName())) {
            return hashCode();
        } else if ("toString".equals(method.getName())) {
            return toString();
        }
        // 如果是用户自定义的远程调用方法,则执行MethodHandler中的invoke方法
        return dispatch.get(method).invoke(args);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof FeignInvocationHandler) {
          FeignInvocationHandler other = (FeignInvocationHandler) obj;
          return target.equals(other.target);
      }
      return false;
    }
    @Override
    public int hashCode() {
        return target.hashCode();
    }
    @Override
    public String toString() {
        return target.toString();
    }
}

FeignInvocationHandler 还是非常简单的,dispatch就是我们上文提到的methodToHandler(method→MethodHandler)映射,在执行invoke方法时:

  • 如果是Object方法,则调用重写的方法处理
  • 如果是default方法,则从dispatch映射中获取对应的DefaultMethodHandler.invoke()处理
  • 如果是用户定义的远程调用方法,则从dispatch映射中获取对应的SynchronousMethodHandler.invoke()处理

这里我们仅关心远程调用的实现机制,因此下面我们将进入到SynchronousMethodHandler中,观察invoke()方法的执行逻辑:

@Override
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            // 具体执行远程调用的方法
            return executeAndDecode(template, options);
        } catch (RetryableException e) {
            try {
                // 判断是否需要重试
                retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
                Throwable cause = th.getCause();
                if (propagationPolicy == UNWRAP && cause != null) {
                    throw cause;
                } else {
                    throw th;
                }
            }
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}

invoke方法将具体的远程调用委托给executeAndDecode()执行,从方法名可知,该方法不仅执行http远程调用,同时还会对response进行节码操作,这也是Feign非常方便的一点,能够让开发者忽略http报文解析的过程。

invoke还提供了失败重试机制,主要逻辑由Retryer 这个对象实现,感兴趣的小伙伴可以自行了解。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 构造请求对象
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
        logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
        // 执行http请求
        response = client.execute(request, options);
        // ensure the request is set. TODO: remove in Feign 12
        response = response.toBuilder()
            .request(request)
            .requestTemplate(template)
            .build();
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        throw errorExecuting(request, e);
    }
    // ...忽略解码过程
}

executeAndDecode()方法首先构造请求对象,然后使用client对象发起http请求。之前说了,client可以是Apache HttpClient或者Feign封装的具有负载均衡能力的FeignBlockingLoadBalancerClient或者RetryableFeignBlockingLoadBalancerClient,但这两个client的execute()方法底层最终会调用其中的delegate(即Apache HttpClient)执行http远程调用。

讲到这里,FeignClientFactoryBean#getObject方法的执行逻辑我们也非常清楚了,就是通过JDK的动态代理,对Apache HttpClient进行多层封装,以实现远程调用的能力。这里也用一张图梳理下整个过程:

在这里插入图片描述

4、总结

本文主要介绍了Feign的自动装载和动态代理机制,并梳理了这两个机制的主要脉络,而忽略其它次要信息。
Feign能够被众多开发人员所使用绝不仅仅是具备以上介绍的两个功能,它还提供了诸如负载均衡、熔断等机制,这些也在文章中有少量提及,大家感兴趣的话,可以沿着本文所介绍的主脉络,一一梳理下这些功能。

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

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

相关文章

Lunabot一款无需密钥魔法就能使用到ChatGPT的浏览器工具

Lunabot 简介 Lunabot是一款跨平台的ChatGPT助手&#xff0c;支持主流浏览器上的任意网页&#xff0c;可以快速处理各种任务&#xff0c;提高您的工作效率。使用 Lunabot&#xff0c;您可以轻松选择文本并使用快速命令和提示来快速处理任务&#xff0c;同时还可以保存永远不会…

Node【二】NPM

文章目录&#x1f31f;前言&#x1f31f;NPM使用&#x1f31f;NPM使用场景&#x1f31f;NPM的常用命令&#x1f31f;NPM命令使用介绍&#x1f31f; 使用NPM安装模块&#x1f31f; 下载三方包&#x1f31f; 全局安装VS本地安装&#x1f31f; 本地安装&#x1f31f; 全局安装&…

研读Rust圣经解析——Rust learn-5(所有权,强大的String)

研读Rust圣经解析——Rust learn-5&#xff08;所有权,强大的String&#xff09;所有权栈和堆相同点栈堆所有权规则作用域StringString创建String创建空字符串从字符串字面量创建&#xff08;将&str转化为String&#xff09;str特点创建str所有权转移String源码深克隆clone…

Matplotlib基本图形使用折线图-柱状图-散点图-饼图的完整代码含示例

目录 Matplotlib基本折线图的使用 1. 导入matplotlib库 2. 准备数据 3. 绘制折线图 4. 加上标签和标题 5. 自定义样式 6. 完整代码 ​编辑 绘制散点图 导入 matplotlib 库和 numpy 库 准备数据 绘制散点图 ​编辑 绘制饼图 导入 matplotlib 库和 numpy 库 准备数…

JS尺寸相关

文章目录元素偏移量 offset 系列offset 系列常用属性offset 与 style 区别获取鼠标在盒子内的坐标模态框放大镜效果元素client 系列元素client系列属性元素滚动 scroll 系列元素 scroll 系列属性页面被卷去的头部兼容性解决方案监听滚动效果仿淘宝固定侧边栏缓动动画筋斗云案例…

实战打靶集锦-015-djinn3

提示&#xff1a;本文记录了作者一次曲折的打靶提权经历 目录1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 Lighttpd探查4.1.1 浏览器探查4.1.2 EXP搜索4.1.2.1 CVE-2019-110724.1.2.2 CVE-2018-190524.2 Werkzeug探查4.2.1 浏览器探查4.2.2 EXP搜索4.2.2.1 目录遍历4.2.2…

Stable Diffusion Web UI + Anaconda环境 + 本地Windows系统部署

Stable Diffusion Web UI Anaconda环境 本地Windows系统部署 最近的很多AIGC模型层出不穷&#xff0c;Stable Diffusion 模型作为一个开源的热门生成式模型&#xff0c;或许对未来的各行各业都能产生深远的影响&#xff0c;了解这个模型并会使用可能是很多人目前想要学习的&…

车载Mini LED持续升温,各家厂商进展如何?

去年&#xff0c;Mini LED背光技术在车载显示赛道上初露头角&#xff0c;多款搭载 Mini LED 屏幕的汽车陆续发布。随着新能源车渗透率的提高&#xff0c;车载显示成为明确增长的赛道&#xff0c;为Mini LED背光进入车载带来利好。 结合今年各家厂商披露的信息来看&#xff0c…

浮点型数据在内存的存储方式

目录 大体规则 特殊规定 由于浮点型在内存中的存储方式相较于整型的要复杂一些&#xff0c;而且很容易忘掉&#xff0c;所以就将部分知识点整理了一下&#xff0c;写成一篇博客。 大体规则 根据国际标准&#xff08;电气和电子工程协会&#xff09;IEEE 754&#xff0c;任意…

【数据结构】- 初识数据结构之空间复杂度(下)

文章目录前言一、空间复杂度1.1空间复杂度简解1.2常见空间复杂度的计算举例二、常见复杂度的对比总结前言 将喜欢的一切留在身边 这便是努力的意义. 本章是关于初识数据结构之空间复杂度(下) 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、空间复…

真的干不过,00后整顿职场已经给我卷麻了,想离职了...

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&#x…

D. Li Hua and Tree(set操作)

Problem - D - Codeforces 李华有一个有n个顶点和n -1条边的树。树的根是顶点1。每个顶点i的重要性为a。将子树的大小表示为该子树中顶点的数量&#xff0c;将重要性表示为该子树中顶点的重要性之和。将非叶顶点的重子结点表示为具有最大子树大小的子结点。如果存在多个重子&am…

安全防御 --- 入侵检测 --- IDS、IPS

入侵检测 1、入侵检测经典理论 系统访问控制要针对三类用户 &#xff08;1&#xff09;合法用户 &#xff08;2&#xff09;伪装 --- 攻破[流程控制]&#xff08;超出了合法用户的行为范围&#xff09; 身份仿冒&#xff08;可能是最早提出不能仅依赖于身份认证&#xff0c;还…

STM32F4+FreeRTOS+LVGL实现嵌入式快速开发(缝合怪)

极速进行项目开发&#xff0c;只需要懂一款芯片架构一个操作系统一个GUI。各种部件程序全靠抄 &#xff0c;成为究极缝合怪。本文用stm32f407FreeRTOSlvgl演示一些demo。 原文链接&#xff1a;STM32F4FreeRTOSLVGL实现快速开发(缝合怪) lvgl官方的音乐播放器demo&#xff1a;…

微信小程序学习笔记

一、Node.js主题 1、npm&#xff1a;node.js包管理工具&#xff0c;有超过60万个JavaScript代码包可供下载 2、Node.js&#xff1a;运行在服务端的JavaScript&#xff0c;基于Chrome JavaScript建立的一个平台&#xff0c;基于Google V8引擎。 3、Nodejs安装教程&#xff1a…

Redis篇之主从复制及哨兵模式

主从复制及哨兵模式 1、概念 主从复制&#xff1a; 是指将一台 Redis 服务器的数据&#xff0c;复制到其他的 Redis 服务器。前者称为主节点&#xff08;Master/Leader&#xff09;,后者称为从节点&#xff08;Slave/Follower&#xff09;&#xff0c; 数据的复制是单向的&a…

毕业设计 基于51单片机的智能水表水流量计流量报警器温度设计

基于STM32的语音IC卡停车管理系统1、快速完成毕设的方法2、项目资料2.1 系统框架2.2 系统功能3、部分电路设计3.1 STC89C52单片机最小系统电路设计3.2 继电器控制电路设计3.3 DS18B20温度检测电路设计3.4 LCD1602液晶显示电路设计4、部分代码展示4.1 LCD1602液晶显示屏引脚初始…

2023年第十四届蓝桥杯 C++ B组参赛经验总结

没错&#xff0c;今年本菜狗又来啦~~ hhh &#xff0c; 文章当时比赛完就写完了&#xff0c; 发的有点晚 比赛成绩 &#xff08;等出来我就写这里&#xff09; 感觉最多省二 估计没省一了555 赛前准备 赛前把蓝桥杯课基本都刷了 &#xff0c; 但是还是感觉有点慌 刷题经验 …

【mysql是怎样运行的】-B+树索引深入理解

文章目录1. 无索引查找方式1.1 在一个页中查找1.2 在多个页中查找2. 索引3. 简易索引方案4. InnoDB 中的索引方案5. **常见索引概念**数据页与记录关系&#xff1a;各个数据页可以组成一个 双向链表&#xff0c;而每个数据页中的记录会按照主键值从小到大的顺序组成一个 单向链…

Linux主机上的用户信息传递(查询用户(w,who,last,lastlog),用户对谈(write,mesg,wall),用户邮箱mail)

文章目录Linux主机上的用户信息传递查询用户&#xff1a;w、who、last、lastlog用户对谈&#xff1a;write、mesg、wall用户邮箱&#xff1a;mail使用案例给自己的QQ邮箱发送一封邮件①获取授权码②使用mailx发送邮件③测试是否可以发送邮件Linux主机上的用户信息传递 想过吗如…