feign远程调用原理

news2024/10/7 5:50:32

目录

一、简介

二、调用流程分析

2.1 添加注解

 2.2 @Import(FeignClientsRegistrar.class),

2.3 代理创建流程

2.4 代理调用


一、简介

        feign是springCloud全家桶中的远程调用组件,其底层主要依赖于Java的动态代理机制,然后基于http client进行http请求,同时它还能配合其它组件实现Loadbalance(负载均衡)、Hystrix(熔断)、fallback(降级)等功能。

二、调用流程分析

 

2.1 添加注解

在application启动类上添加@EnableFeignClients注解,并在basePackages属性中添加FeignClient所在的包,使Spring容器能够扫描到所有的FeignClient对象,这样就可以开始使用feign的远程调用功能了,EnableFeignClients包含以下属性,常用的是basePackages。

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

	/**
	 * basePackages() 属性的别名
	 */
	String[] value() default {};

	/**
	 * 注解扫描包路径集
	 */
	String[] basePackages() default {};

	/**
	 * basePackages的替代值,注解扫描类所在的每个包,
     * 若使用此属性,会在每个feign类所在包下建立一个特殊意义的空类
	 */
	Class<?>[] basePackageClasses() default {};

	/**
	 * 针对所有client生效的注解类集
	 */
	Class<?>[] defaultConfiguration() default {};

	/**
	 * 标注有@FeignClient注解的类集合,若不为空,则禁止路径扫描
	 * @return
	 */
	Class<?>[] clients() default {};
}

 2.2 @Import(FeignClientsRegistrar.class),

        将feign相关对象注入到容器中,导入了 FeignClientsRegistrar注册器,该类实现了spring提供的ImportBeanDefinitionRegistrar接口,能够在registerBeanDefinitions方法中注册自定义的bean。同时也实现了ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware等钩子接口,能够持有spring的环境、资源等变量,方便进行包扫描。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

	private ResourceLoader resourceLoader;
	private ClassLoader classLoader;
	private Environment environment;

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //从@EnableFeignClients注解中提取defaultConfiguration属性配置的key和value,并将配置类注册至spring容器
		registerDefaultConfiguration(metadata, registry);
        //扫描所有的feignclient类,注册进spring容器
		registerFeignClients(metadata, registry);
	}
}

        registerFeignClients方法:注册所有feign逻辑

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);        
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
        //若clients为空,则进行包扫描
		if (clients == null || clients.length == 0) {
            //添加注解过滤条件
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}else {
        //不为空直接注入
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}
        //遍历包路径集
		for (String basePackage : basePackages) {
            //scanner扫描所有符合要求的bean
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    //校验是否为接口,若不为则抛异常,@FeignClient只能在接口上使用
					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"));
                    //注册FeignClient
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

        registerFeignClient方法:实际注册单个FeignClient的逻辑

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
        //获取FeignClientFactoryBean的BeanDefinitionBuilder
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
        //属性校验
		validate(attributes);
        //bean属性添加
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
        //class类型
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
        //设置降级工厂
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        //设置自动注入类型
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = name + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

		boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

		beanDefinition.setPrimary(primary);
        //设置别名
		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
        //注册BeanDefinition
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

        到这里,我们所有的feignClient bean对象就已经被注入到spring容器中了,就可以正常使用feignClient接口去远程调用了。

2.3 代理创建流程

        FeignClientFactoryBean工厂类,用于提供FeignClient实例,其关键方法如下:

    public Object getObject() throws Exception {
        //获取feign的上下文
		FeignContext context = applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
        //若未指定url,则根据名称去构建feignClient,且具备负载均衡能力
		if (!StringUtils.hasText(this.url)) {
			String url;
			if (!this.name.startsWith("http")) {
				url = "http://" + this.name;
			}
			else {
				url = this.name;
			}
			url += cleanPath();
			return loadBalance(builder, context, new HardCodedTarget<>(this.type,
					this.name, url));
		}
        //若url不为空,代表指定了某台机器,不需要负载均衡
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not lod balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
                // 解除负载均衡能力
				client = ((LoadBalancerFeignClient)client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
        //真正的获取方法
		return targeter.target(this, builder, context, new HardCodedTarget<>(
				this.type, this.name, url));
	}

        真正的获取方法其实是targeter.target()方法,targeter包含两个实现:

  • DefaultTargeter:默认实现,直接调用Feign.Builder的target方法;
  • HystrixTargeter:具备限流熔断机制,若builder不属于HystrixFeign类型,则直接调用Feign.Builder的target方法。若属于,则将builder强转为feign.hystrix.HystrixFeign.Builder类型,然后按顺序调用factory.getFallback()或factory.getFallbackFactory(),根据fallback或fallbackFactory是否为void.class,则调用targetWithFallback或targetWithFallbackFactory构建对象。若都为void.class,则直接调用Feign.Builder的target方法。
	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
						Target.HardCodedTarget<T> target) {
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
		SetterFactory setterFactory = getOptional(factory.getName(), context,
			SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(factory.getName(), context, target, builder, fallback);
		}
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
		}

		return feign.target(target);
	}

	private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
											Target.HardCodedTarget<T> target,
											HystrixFeign.Builder builder,
											Class<?> fallbackFactoryClass) {
		FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
			getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
		/* We take a sample fallback from the fallback factory to check if it returns a fallback
		that is compatible with the annotated feign interface. */
		Object exampleFallback = fallbackFactory.create(new RuntimeException());
		Assert.notNull(exampleFallback,
			String.format(
			"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",
				feignClientName));
		if (!target.type().isAssignableFrom(exampleFallback.getClass())) {
			throw new IllegalStateException(
				String.format(
					"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",
					feignClientName, exampleFallback.getClass(), target.type()));
		}
		return builder.target(target, fallbackFactory);
	}


	private <T> T targetWithFallback(String feignClientName, FeignContext context,
									 Target.HardCodedTarget<T> target,
									 HystrixFeign.Builder builder, Class<?> fallback) {
		T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
		return builder.target(target, fallbackInstance);
	}

        翻看源码调用链,会发现不管是HystrixFeign.Builder的target方法还是Feign.Builder的target方法,最终都会调用Feign接口的newInstance方法,该方法默认实现在ReflectiveFeign类中,这个里面就能看到熟悉的Proxy,也就是jdk动态代理

/**
 * 创建绑定到目标的 API。由于这会调用反射,因此应注意缓存结果。
 */
@Override
  public <T> T newInstance(Target<T> target) {
    //创建configKey→SynchronousMethodHandler的映射,基本上是SynchronousMethodHandler,主要用于处理用户自定义的方法
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    //存储处理用户定义的FeignClient接口中的default方法的handler
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    //遍历代理对象中的所有方法
    for (Method method : target.type().getMethods()) {
      //如果是Object中的方法,跳过不处理
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        //如果是default方法,则创建DefaultMethodHandler处理
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        //用户自定义的方法了,此时从nameToHandler中拿出SynchronousMethodHandler进行映射
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //创建InvocationHandler 核心代理对象,代理逻辑都封装在该对象中
    InvocationHandler handler = factory.create(target, methodToHandler);
    //jdk动态代理
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

2.4 代理调用

        远程代理调用核心逻辑在FeignInvocationHandler类的invoke方法中,首先判断是否为equals、hashcode或toString方法,是则直接调用target对象的这些方法。若不是,则根据创建过程中的configKey→MethodHandler的映射获取MethodHandler进行处理。

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      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();
      }
      return dispatch.get(method).invoke(args);
    }

default方法:

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    if(handle == null) {
      throw new IllegalStateException("Default method handler invoked before proxy has been bound.");
    }
    return handle.invokeWithArguments(argv);
  }

 自定义方法:核心逻辑,包含远程调用及重试机制。其中executeAndDecode会执行远程的http调用,同时也会进行http报文的解析,而catch块中会利用retryer进行重试。

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }
  Object executeAndDecode(RequestTemplate template) throws Throwable {
    //构造请求对象
    Request request = targetRequest(template);

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

    Response response;
    long start = System.nanoTime();
    try {
      //client执行http远程调用,client可以是Apache HttpClient或者Feign封装的具有负载均衡能力的FeignBlockingLoadBalancerClient
    //或者RetryableFeignBlockingLoadBalancerClient,
    //但这两个client的execute()方法底层最终会调用其中的delegate(即Apache HttpClient)执行http远程调用
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 10
      response.toBuilder().request(request).build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    //执行时长
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    //解码
    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
                response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          return decode(response);
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        return decode(response);
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

重试机制:retryer.continueOrPropagate(e)

   public void continueOrPropagate(RetryableException e) {
      //重试次数
      if (attempt++ >= maxAttempts) {
        throw e;
      }

      //计算等待时间间隔
      long interval;
      if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        if (interval > maxPeriod) {
          interval = maxPeriod;
        }
        if (interval < 0) {
          return;
        }
      } else {
        interval = nextMaxInterval();
      }
      try {
        Thread.sleep(interval);
      } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
      }
      //总等待时长
      sleptForMillis += interval;
    }

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

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

相关文章

【软件测试】全网火爆,实战Web项目前后台的bug定位(超详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 系统整体架构 Se…

【Java入门合集】第二章Java语言基础(三)

【Java入门合集】第二章Java语言基础&#xff08;三&#xff09; 博主&#xff1a;命运之光 专栏&#xff1a;JAVA入门 学习目标 掌握变量、常量、表达式的概念&#xff0c;数据类型及变量的定义方法&#xff1b; 掌握常用运算符的使用&#xff1b; 掌握程序的顺序结构、选择结…

【微信小程序开发】微信小程序集成腾讯位置项目配置

第一步 进入官网 按照Hello World流程走 腾讯位置服务官网 1、申请密钥 当然没账号的要先注册一个账号 在我的应用里创建一个新的应用&#xff0c;印象中需要小程序ID&#xff0c;去微信开发者工具里面找到自己的小程序ID填入即可 添加key中勾选勾选WebServiceAPI 2、下载S…

锐龙7000PBO温度墙设置

AMD的锐龙7000处理器首发评测大家也都看过了&#xff0c;很多人关心的都是它的性能是否可以超越12代酷睿甚至即将发布的13代酷睿&#xff0c;这方面的测试结果差不多了&#xff0c;但是很多人不知道的是散热问题更需要关注。 在评测中&#xff0c;锐龙9 7950X在拷机时温度达到…

【PCIE体系结构七】数据链路层介绍

&#x1f449;个人主页&#xff1a;highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 参考书籍&#xff1a;PCI_Express体系结构导读 目录 前言 数据链路层概述 数据链路层…

命名ACL配置

命名ACL配置 【实验目的】 掌握命名ACL的配置。验证配置。 【实验拓扑】 实验拓扑如图1所示。 图1 实验拓扑 设备参数如表所示。 表1 设备参数表 设备 接口 IP地址 子网掩码 默认网关 R1 S0/3/0 192.168.1.1 255.255.255.252 N/A Fa0/0 192.168.2.1 255.255.…

05_Uboot源码目录分析

目录 Uboot 源码目录分析 arch 文件夹 board 文件夹 configs 文件夹 .u-boot.xxx_cmd 文件 Makefile 文件 u-boot.xxx文件 .config文件 README Uboot 源码目录分析 学会uboot使用以后就可以尝试移uboot到自己的开发板上了,但是在移植之前需要我们得先分析一遍uboot的…

什么是Spring FactoryBean?有什么作用?

1、什么是Spring Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架&#xff0c;以 IOC和AOP为内核。含有七大核心模块 2、Spring的七大模块 (1)Spring Core&#xff1a;核心容器提供了Spring的基本功能。核心容器的核心功能是用IOC 容器来管理类的依赖关系&#xff…

卷积神经网络详解

&#xff08;一&#xff09;网络结构 一个卷积神经网络里包括5部分——输入层、若干个卷积操作和池化层结合的部分、全局平均池化层、输出层&#xff1a; ● 输入层&#xff1a;将每个像素代表一个特征节点输入进来。 ● 卷积操作部分&#xff1a;由多个滤波器组合的卷积层。 …

788. 逆序对的数量(C++和Python3)——2023.5.2打卡

文章目录 QuestionIdeasCode Question 给定一个长度为 n 的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i 个和第 j 个元素&#xff0c;如果满足 i<j 且 a[i]>a[j] &#xff0c;则其为一个逆序对&#xff1b;否则不…

14-6-进程间通信-信号量

前面学习了pipe,fifo,共享内存&#xff0c;信号。 本章将讲述信号量。 一、什么是信号量/信号量集&#xff1f; 1.什么是信号量 信号量是一个计数器。信号量用于实现进程间的同步和互斥。而可以取多个正整数的信号量被称为通用信号量。 对信号量的使用场景的解读 房间&#…

MyBatis学习记录

文章目录 MyBatis介绍JDBC缺点MyBatis简化MyBatis快速入门之查询user表中的所有数据1、创建user表&#xff0c;添加数据2、创建模块&#xff0c;导入坐标3、编写MyBatis核心配置文件 --> 替换连接信息&#xff0c;解决硬编码问题4、编写SQL映射文件 --> 同一管理sql语句&…

计算机网络:DNS域名解析过程

基本概念 DNS是域名系统&#xff08;Domain Name System&#xff09;的缩写&#xff0c;也是TCP/IP网络中的一个协议。在Internet上域名与IP地址之间是一一对应的&#xff0c;域名虽然便于人们记忆&#xff0c;但计算机之间只能互相认识IP地址&#xff0c;域名和IP地址之间的转…

实例解读nn.AdaptiveAvgPool2d((1, 1))

nn.AdaptiveAvgPool2d((1, 1))在PyTorch中创建一个AdaptiveAvgPool2d类的实例。该类在输入张量上执行2D自适应平均池化。 自适应平均池化是一种池化操作&#xff0c;它计算每个输入子区域的平均值并产生一个指定大小的输出张量。子区域的大小是根据输入张量的大小和输出张量的…

5年测试点工?老鸟总结功能到接口自动化测试进阶,自动化核心竞争力...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 我们来说说 功能测…

权限提升:信息收集 .(Linux系统)

权限提升&#xff1a;信息收集. 权限提升简称提权&#xff0c;由于操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;比如通过 Web 漏洞拿到的是 Web 进程的权限&#xff0c;往往 Web 服务都是以一个权限很低的账号启动的&#xff0c;因此通过 Webshel…

unity 性能优化之GPU和资源优化

Shader相关优化 众所周知&#xff0c;我们在unity里编写Shader使用的HLSL/CG都是高级语言&#xff0c;这是为了可以书写一套Shader兼容多个平台&#xff0c;在unity打包的时候&#xff0c;它会编译成对应平台可以运行的指令&#xff0c;而变体则是&#xff0c;根据宏生成的&am…

【英语】大学英语CET考试,翻译部分(修饰后置,定语从句,插入语,多动句,无主句)

文章目录 3大知识点与出题形式1、修饰后置&#xff08;使用介词&#xff09;2、修饰后置&#xff08;定语从句&#xff08;被逼无奈&#xff09;/which&#xff08;非限制性&#xff0c;加高级&#xff09;&#xff09;3、修饰后置&#xff08;插入语或同位语&#xff08;只有1…

【力扣-20】有效的括号

&#x1f58a;作者 : D. Star. &#x1f4d8;专栏 : 数据结构 &#x1f606;今日分享 : 夏虫不可以语冰 : 出自「庄子秋水」。原句是“井蛙不可以语于海者&#xff0c;拘于虚也&#xff1b;夏虫不可以语于冰者&#xff0c;笃于时也&#xff1b;曲士不可以语于道者&#xff0c;束…

自动驾驶——离散LQR的黎卡提方程Riccati公式推导与LQR工程化

1.LQR Question Background 之前写过连续系统的黎卡提方程Riccati推导&#xff0c;但是考虑到实际工程落地使用的是离散系统&#xff0c;于是又进行了离散黎卡提方程Riccati的公式推导。 2.Proof of Riccati Equation Formula for Discrete Systems 工程化落地&#xff0c;就…