Java反射与“整活--(IOC容器)”

文章目录

  • 前言
  • 反射
    • 什么是反射
    • 基本操作
      • 获取类对象
      • 获取类属性
      • 获取类方法
      • 方法的执行
      • 对构造方法的操作
  • 注解
    • 定义
    • 获取注解
  • 整活(IOC容器)
    • 项目结构
    • IOC/DI流程
      • ApplicationContext
      • BeanDefinitionReader
      • BeanDefinition
      • BeanWrapper
      • getBean()方法
  • IOC完整实现
    • 启动/调用
    • 完整实现

前言

没啥意思,太无无聊了,中值定理玩到吐,最近在复习高数,考研和开发并发进行,恢复博文更新,一方面是为了毕设,另一方面是为了秋招,看看有没有机会。当然考研还是个大方向,但是如果有合适的机会,那么,实话实话,鄙人没有什么远大理想,就搞钱钱,如果去读个研,后面还得装sz拿着可怜,甚至没有的补助的话,那不如去上个班。书什么时候都可以去读,但是机会不见得什么时候都有。至于所谓的人脉,非头部,别说话。那么废话不多说,我们来玩玩今天的反射吧。之后,我们再看看,如何将我们的反射用到我们的实际的项目当中,而不是,一直使用Spring或者SpringBoot。这里我们是做Java程序员,不是Spring
\SpringBoot程序员。你可以说,你常用的玩意是它,但是不能说你只会这玩意。

反射

什么是反射

Java反射是指在运行时动态地获取类的信息并操作对象的能力。通过反射,可以在运行时检查和修改类、方法、字段等的属性和行为,即使在编译时无法确定具体的类结构和方法。

举个例子:假设你拿到了一本书,但是在不打开书的情况下,你只能看到书的封面,而无法知道书中具体的内容。此时,反射就相当于打开这本书,你可以逐页阅读书籍的每个章节、段落和文字,甚至可以修改或添加内容。通过反射,你可以探索和操作类的结构和对象的特性,灵活地进行编程。
Java反射就是提供了一种动态检查和操作类、方法、字段等的能力。 通过反射,我们可以获取到类的属性,方法等等。通过这些内容,我们可以很轻松地完成类的操作。并且可以不进行显示的new对象。方便进行扩展,等操作。

基本操作

概念的话,我就念到这里,说实话,作用不大,直接看代码作用更大。

获取类对象

在Java中,可以通过以下三种方式来获取类对象:

  1. 使用Class.forName()方法:
    这是一种常用的方式,通过提供类的全限定名作为参数,返回对应的Class对象。例如:

    Class<?> clazz = Class.forName("com.example.MyClass");
    
  2. 使用类字面常量:
    可以直接使用类字面常量来获取类对象,这是一种更加简洁方便的方式。例如:

    Class<?> clazz = MyClass.class;
    
  3. 调用对象的getClass()方法:
    在已经有一个对象的情况下,可以通过调用该对象的getClass()方法来获取类对象。例如:

    MyClass obj = new MyClass();
    Class<?> clazz = obj.getClass();
    

获取类属性

要获取类的属性,可以使用以下方法:

  1. 使用Class对象的getFields()方法:
    该方法返回类的所有公共字段(包括继承的字段),并以Field对象数组的形式返回。例如:

    Class<?> clazz = MyClass.class;
    Field[] fields = clazz.getFields();
    
  2. 使用Class对象的getField(String name)方法:
    通过指定字段名称,可以获取类的特定公共字段。如果字段不存在或不可访问,则会抛出NoSuchFieldException异常。例如:

    Class<?> clazz = MyClass.class;
    Field field = clazz.getField("fieldName");
    
  3. 使用Class对象的getDeclaredFields()方法:
    该方法返回类的所有字段(不包括继承的字段),并以Field对象数组的形式返回。例如:

    Class<?> clazz = MyClass.class;
    Field[] fields = clazz.getDeclaredFields();
    
  4. 使用Class对象的getDeclaredField(String name)方法:
    通过指定字段名称,可以获取类的特定字段,无论其访问权限如何。如果字段不存在,则会抛出NoSuchFieldException异常。例如:

    Class<?> clazz = MyClass.class;
    Field field = clazz.getDeclaredField("fieldName");
    

这些方法提供了不同的方式来获取类的属性信息,包括公共字段和私有字段。需要注意的是,不过对于私有字段,需要通过setAccessible(true)方法设置访问权限,才能对其进行操作。此外,还可以通过Field对象的getName()、getType()等方法获取字段的名称、类型等相关信息。

此外我们还可以通过如下方法来对类的属性进行操作:

使用Field对象的set(Object obj, Object value)方法:
首先需要获取对应的Field对象,然后使用set方法设置属性的值。第一个参数是要设置属性值的对象实例,如果是静态字段,则可以传入null;第二个参数是要设置的属性值。例如:

Class<?> clazz = MyClass.class;
Field field = clazz.getDeclaredField("fieldName");
field.setAccessible(true); // 设置访问权限
Object obj = new MyClass();
field.set(obj, value); // 设置属性值

此外还要使用Field对象的setInt(Object obj, int value)、setDouble(Object obj, double value)等类型特定的方法,无法返回对象是你特定的,而不是Object罢了。

获取类方法

之后是获取到类的方法

  1. 使用Class对象的getMethods()方法:
    该方法返回类的所有公共方法(包括继承的方法),并以Method对象数组的形式返回。例如:

    Class<?> clazz = MyClass.class;
    Method[] methods = clazz.getMethods();
    
  2. 使用Class对象的getMethod(String name, Class<?>… parameterTypes)方法:
    通过指定方法名称和参数类型,可以获取类的特定公共方法。如果方法不存在或不可访问,则会抛出NoSuchMethodException异常。例如:

    Class<?> clazz = MyClass.class;
    Method method = clazz.getMethod("methodName", String.class, int.class);
    
  3. 使用Class对象的getDeclaredMethods()方法:
    该方法返回类的所有方法(不包括继承的方法),并以Method对象数组的形式返回。例如:

    Class<?> clazz = MyClass.class;
    Method[] methods = clazz.getDeclaredMethods();
    
  4. 使用Class对象的getDeclaredMethod(String name, Class<?>… parameterTypes)方法:
    通过指定方法名称和参数类型,可以获取类的特定方法,无论其访问权限如何。如果方法不存在,则会抛出NoSuchMethodException异常。例如:

    Class<?> clazz = MyClass.class;
    Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
    

这些方法提供了不同的方式来获取类的方法信息,包括公共方法和私有方法。需要注意的是,对于私有方法,需要通过setAccessible(true)方法设置访问权限,才能对其进行调用。此外,还可以通过Method对象的getName()、getReturnType()等方法获取方法的名称、返回类型等相关信息。

方法的执行

现在,我们获取到了这个方法还不够,我们还需要进行执行到这个方法,不然获取这个玩意有啥。

我们从头捋一遍:

  1. 获取方法对象:
    首先,需要获取目标类的Class对象,然后使用getMethod()getDeclaredMethod()方法获得特定的方法对象。例如:

    Class<?> clazz = MyClass.class;
    Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
    
  2. 获取方法的输入参数信息:
    使用方法对象的getParameterTypes()方法获取方法的参数类型数组。例如:

    Class<?>[] parameterTypes = method.getParameterTypes();
    
  3. 构造方法的参数值:
    根据参数类型,构造一个与方法参数一一对应的参数值数组。例如:

    Object[] arguments = new Object[parameterTypes.length];
    arguments[0] = "example";
    arguments[1] = 10;
    
  4. 设置私有方法可访问:
    如果是私有方法,需要设置方法的可访问性:

    method.setAccessible(true);
    
  5. 执行方法并获取返回值:
    使用方法对象的invoke()方法执行方法,并获取返回值。例如:

    Object result = method.invoke(obj, arguments);
    

对构造方法的操作

这个没啥,就是用这个玩意来newInstance()一个对象,就没了。
其他的作用不大,就玩玩这些对象就够用了。

注解

说到了,这个反射,那肯定是不够的,俺们还有注解没有说。

注解(Annotation)是Java语言提供的一种元数据(Metadata)机制,它可以用于在类、方法、字段等程序元素上添加额外的信息。注解可以在编译时和运行时被读取和处理,用于对代码进行标记、描述或配置。

定义

Java中的注解使用@符号来标识,放置在目标元素前面。例如,常见的注解包括:

  1. @Override:用于标识方法覆盖父类的方法。
  2. @Deprecated:用于标识过时的方法或类。
  3. @SuppressWarnings:用于抑制编译器警告。
  4. @Entity:用于标识JPA实体类。
  5. @RequestMapping:用于标识Spring MVC控制器方法的映射路径。

此外,我们还可以通过@interface关键字来定义一个新的注解类型。例如:

import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {String value();int count() default 0;
}

上述代码定义了一个名为MyAnnotation的自定义注解,包含了一个value属性和一个count属性。注解的生命周期为运行时,并且只能应用于方法上。

此外,对于注解,我们还有对注解的一些描述,这些描述,是对注解进行的一些说明,描述,并给JVM进程一定的操作。例如这个注解作用到哪里,什么时候执行等等。这些被称为元注解。
例如:

import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)

以下是Java中常用的元注解:

  1. @Retention:用于指定注解的生命周期,包括三个取值:

    • RetentionPolicy.SOURCE:注解仅存在于源代码中,在编译时会被忽略。
    • RetentionPolicy.CLASS:注解会被保留在编译后的字节码文件中,但在运行时不可用。
    • RetentionPolicy.RUNTIME:注解会被保留在编译后的字节码文件中,并在运行时可以通过反射读取。
  2. @Target:用于指定注解适用的目标元素类型,包括多个取值:

    • ElementType.TYPE:类、接口、枚举等。
    • ElementType.FIELD:字段、枚举常量。
    • ElementType.METHOD:方法。
    • ElementType.PARAMETER:方法参数。
    • ElementType.CONSTRUCTOR:构造函数。
    • ElementType.LOCAL_VARIABLE:局部变量。
    • ElementType.ANNOTATION_TYPE:注解类型。
    • ElementType.PACKAGE:包。
    • ElementType.TYPE_PARAMETER:泛型参数。
    • ElementType.TYPE_USE:类型使用。
  3. @Documented:用于指定注解是否包含在Java文档中。

  4. @Inherited:用于指定注解是否可以被继承。

获取注解

通过,这个玩意,我们可以获取到类上面的注解

 Annotation[] annotations = clazz.getAnnotations();

此外,我们还可以获取值:
这是一段示例

import java.lang.annotation.Annotation;@MyAnnotation(name = "MyClass", version = 1.0)
public class MyClass {public static void main(String[] args) {Class<?> clazz = MyClass.class;// 获取类的注解Annotation[] annotations = clazz.getAnnotations();for (Annotation annotation : annotations) {if (annotation instanceof MyAnnotation) {MyAnnotation myAnnotation = (MyAnnotation) annotation;String name = myAnnotation.name();double version = myAnnotation.version();System.out.println("Class Name: " + name);System.out.println("Version: " + version);}}}
}

当然除此之外,我们还可以获取方法上面的。
其实是一样的:

Annotation[] annotations = method.getAnnotations();

其他啥也没变。

整活(IOC容器)

现在基础概念和使用方法,过了一遍了,那么我们接下来就要进行简单的运用了,如何把这个玩意用在我们实际的项目开发当中。那么接下来我们来编写一个,简单的项目架子。以后在些一些非Web应用的时候也用上来。虽然简陋,但是性能绝对很牛逼(因为简单,用安全性换的)。

来,先看到,俺们的项目,啥也没有:
在这里插入图片描述
在未来,这个项目将变得非常复杂,就像这样:
在这里插入图片描述
并且在这些玩意里面有非常多的东西需要处理。不过在这里,不管这个项目有多复杂。有一个亘古不变的东西,那就是,这些代码分为两大阵营,第一大阵营是苦力工,专门负责运送,加工数据。另一大阵营是数据载体,专门负责运载数据。也就是各种Service,Implement,和Dao,Entity,只不过这些苦力有各种各样的工种,有些负责和数据库打交道,有些负责和其他第三方or rpc接口打交道。同样的对于数据载体也是不同的,有很多类别

对于数据载体来说,每一个载体都是独一无二的,因为,运载的数据不同。但是一个苦力却可以一直不断地重复处理他所对应的数据。这是啥意思咧,每次我来了一个新的数据,需要处理的时候,我需要找个苦力来进行处理,比如,有一个数据负责用户的注册,我此时就需要一个员工来处理这件事情,并且从事UserService有3年工作检验,来帮我把这个数据,运送到叫做Mysql的地方去,有些有钱的老板还会放到叫做Oracle的地方。不过大部分的老板都没钱,于是只能放到叫做Mysql的地方。当然有时候也会放到redis,mongdb,kafka,es, 等等地方去。这个时候,我有两个选择,要么当数据运算完毕之后,就开除这个员工,要么,先把这个员工招进来,等我需要处理这个数据的时候,就叫他来。如果长时间业务不好,那就再裁掉这个员工。由于每次都招的话,实在是太慢了,于是你选择养起来。招聘员工,先养着,后来随着业务不断扩大,你管理起来越发麻烦了,于是你成立了一个叫做人事部门,当你需要谁去搬砖的时候,就通知人事,叫他过去就好了,当业务不好的时候,再通知人事裁员就好了,最后再把人事裁了。

那么这个小案例,就说明到了,我们要整活的玩意,就是做一个简单的IOC容器,然后实现控制反转。

项目结构

在这里插入图片描述
其实我们的核心很简单,就是希望通过ApplicationContext 这个玩意可以帮助我拿到类。其他的没啥了,然后这个玩意做为路口,负责我们整个项目的处理,就这么简单。

那么怎么完成管理呢,其实最简单的办法其实就是,把对应的信息放在Map里面不就好了。

IOC/DI流程

在这里插入图片描述

ApplicationContext

这个呢,是咱们整个IOC的入口,其实在Spring里面有一个BeanFactory这个是顶层接口,然后那个ApplicationContext是那个接口的实现。这里没有搞那么复杂,不过流程大体流程是这样子的。

这个ApplicationContext提供了一个非常重要的方法,就是getBean()方法。这个方法就可以帮我们把那个容器里面的对象拿出来。

BeanDefinitionReader

这个是一个解析器嘛,负责解析各种配置文件,比如xml,yaml,properites之类的。
解析之后,要做的是把这个配置文件对应的内容给解析出来,然后把东西封装到BeanDefinition里面

BeanDefinition

这个玩意呢,是封装的我们的类的(被扫描出来)的信息。比如
我们在配置文件里面写的玩意是
在这里插入图片描述
我们要扫描这个包下面的所有类,那么这个扫描首先是reader读取了要扫描包的信息,然后reader去扫描
之后把扫描结果,类名,全包名封装起来。下面是它的代码,可以看到。

package com.huterox.spring.framework.beans.config;public class HUBeanDefinition {private String factoryBeanName;private String beanClassName;public void setFactoryBeanName(String factoryBeanName) {this.factoryBeanName = factoryBeanName;}public void setBeanClassName(String beanClassName) {this.beanClassName = beanClassName;}public String getFactoryBeanName() {return factoryBeanName;}public String getBeanClassName() {return beanClassName;}
}

BeanWrapper

这玩意呢,其实是实例化后的对象,同样我们也是把这玩意封装起来了。
在这里插入图片描述
然后我们可以再看到这个代码

package com.huterox.spring.framework.beans;public class HUBeanWrapper {private Object wrapperInstance;private Class<?> wrapperClass;public HUBeanWrapper(Object instance) {this.wrapperInstance = instance;this.wrapperClass = this.wrapperInstance.getClass();}public Object getWrapperInstance() {return wrapperInstance;}public Class<?> getWrapperClass() {return wrapperClass;}
}

getBean()方法

最后就是我们的这个方法,这个方法呢,是我们的核心
在这里插入图片描述

在这里插入图片描述

IOC完整实现

启动/调用

接下来咱们好好聊聊这个IOC

在这里插入图片描述
在要的地方进行启动就可以,这个有点操作系统的意思在里面的,你写的应用程序只是其中一半,另一半是我底层些的系统库,并且这些系统库大部分都是用汇编进行编写的。(还是做上层简单)

完整实现

ok,我们现在来看到完整的代码:

```java
package com.huterox.spring.framework.context;import com.huterox.spring.framework.annotation.HUAutowired;
import com.huterox.spring.framework.annotation.HUController;
import com.huterox.spring.framework.annotation.HUService;
import com.huterox.spring.framework.beans.HUBeanWrapper;
import com.huterox.spring.framework.beans.suports.HUBeanDefinitionReader;
import com.huterox.spring.framework.beans.config.HUBeanDefinition;import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class HUApplicationContext {private String[] configLocations;private HUBeanDefinitionReader reader;private Map<String, HUBeanDefinition> beanDefinitionMap = new HashMap<>();private Map<String, HUBeanWrapper> factoryBeanInstanceCache = new HashMap<>();private Map<String, Object> factoryBeanObjectCache = new HashMap<>();public HUApplicationContext(String... configLocations) {this.configLocations = configLocations;//加载配置文件读取器this.reader = new HUBeanDefinitionReader(this.configLocations);List<HUBeanDefinition> beanDefinitions = this.reader.doLoadBeanDefinitions();//2.缓存beanDefinitions对象try {doRegistryBeanDefintition(beanDefinitions);} catch (Exception e) {e.printStackTrace();}//3.创建IOC容器,把类都放在IOC容器里面doCreateBean();}private void doCreateBean() {for (Map.Entry<String, HUBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {String beanName = beanDefinitionEntry.getKey();//非延时加载getBean(beanName);}}private void doRegistryBeanDefintition(List<HUBeanDefinition> beanDefinitions) throws Exception {//把Definition对象放在咱们的map里面,beanDefinition是保存的类名,全包名for (HUBeanDefinition beanDefinition : beanDefinitions) {//双键存储便于双向查找if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {throw new Exception("The " + beanDefinition.getFactoryBeanName() + " is exists!");}this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);}}//创建Bean的实例,完成依赖注入public Object getBean(String beanName) {//得到对象的全包名,当然这个信息封装在beanDefinition里面HUBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);//实例化Object instance = instanceBean(beanName, beanDefinition);if (instance == null) return null;//实例封装为beanWrapper当中HUBeanWrapper beanWrapper = new HUBeanWrapper(instance);//将这个wrapper对象放在容器里面IOC里面this.factoryBeanInstanceCache.put(beanName, beanWrapper);//完成依赖注入populateBean(beanName, beanDefinition, beanWrapper);return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();}private void populateBean(String beanName, HUBeanDefinition beanDefinition, HUBeanWrapper beanWrapper) {//开始做依赖注入给值Object instance = beanWrapper.getWrapperInstance();Class<?> clazz = beanWrapper.getWrapperClass();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {if (!(field.isAnnotationPresent(HUAutowired.class))) continue;HUAutowired autowired = field.getAnnotation(HUAutowired.class);String autowiredBeanName = autowired.value().trim();if ("".equals(autowiredBeanName)) {autowiredBeanName = field.getType().getName();}field.setAccessible(true);try {if (!this.factoryBeanInstanceCache.containsKey(autowiredBeanName)) continue;//这个就是为什么要按照标准来写首字母要小写field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());} catch (IllegalAccessException e) {e.printStackTrace();}}}private Object instanceBean(String beanName, HUBeanDefinition beanDefinition) {String className = beanDefinition.getBeanClassName();Object instance = null;try {Class<?> clazz = Class.forName(className);if (!(clazz.isAnnotationPresent(HUService.class) || clazz.isAnnotationPresent(HUController.class))) {return null;}instance = clazz.newInstance();//接下来的这部分是AOP的入口,先忽略//三级缓存,为了解决这种循环依赖的问题,所以在后面进行DI的时候,这里//先进行一个缓存。因为完整的生命周期是对象要完成DI注入后,如果没有先进行缓存//那么在同一时刻进行DI注入的时候,出现A依赖B,B依赖A,并且缓存池里面没有B,要创建B//然后B又要A,A因为没有B又创建不了的情况,先初步缓存这样A要B的时候,创建B然后要依赖A//此时A在创建的时候有个缓存,这样B可以被创建,然后A最后完成注入,同样B也会完成。这部分逻辑后面再说this.factoryBeanObjectCache.put(beanName, instance);} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {e.printStackTrace();}return instance;}public Object getBean(Class className) {return getBean(className.getName());}public int getBeanDefinitionCount() {return this.beanDefinitionMap.size();}public String[] getBeanDefinitionNames() {return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);}
}

可以发现,其实这里没啥太多东西,我们目前按照咱的CURD编码习惯,去掉SpringBoot,去掉Mybatis依赖,其实也可以编写出来Lite的Lite版本。

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

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

相关文章

Django_admin数据管理后台

目录 一、基础操作 二、自定义后台操作数据行为 源码等资料获取方法 admin数据管理后台是django内置的一个后台管理界面&#xff0c;能查看已注册模型类的数据结构&#xff0c;以及对数据的增删改。 一、基础操作 1.1 检查项目目录下的urls.py有没有如下配置 1.2 创建djan…

技术讨论:我心中TOP1的编程语言

欢迎关注博主 六月暴雪飞梨花 或加入【六月暴雪飞梨花】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术…

Flutter——最详细(NavigationBar)使用教程

NavigationBar简介 Material 3 导航栏组件! 导航栏提供了一种持久且便捷的方式来在应用程序的主要目的地之间进行切换。 使用场景&#xff1a; 底部菜单栏模块 属性作用onDestinationSelected选择索引回调监听器selectedIndex目前选定目的地的索引destinations存放菜单按钮back…

七大排序算法——冒泡排序,通俗易懂的思路讲解与图解(完整Java代码)

文章目录 一、排序的概念排序的概念排序的稳定性七大排序算法 二、冒泡排序核心思想代码实现 三、性能分析四、七大排序算法 一、排序的概念 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或…

C++之工厂模式

目录 一、为什么要使用工厂模式 优点 缺点 二、简单工厂&#xff08;Simple Factory&#xff09; 好处&#xff1a; 不足&#xff1a; 三、工厂方法&#xff1a; 好处&#xff1a; 不足&#xff1a; 四、抽象工厂&#xff08;Abstract Factory&#xff09; 一、为什…

【HCIA】10.VLAN间通信

VLAN间通信的解决方法 使用路由器的物理接口 路由器三层接口作为网关&#xff0c;转发本网段前往其它网段的流量。路由器三层接口无法处理携带VLAN Tag的数据帧&#xff0c;因此交换机上联路由器的接口需配置为Access。路由器的一个物理接口作为一个VLAN的网关&#xff0c;因此…

Django_re_path_使用正则匹配url

与path定义的路由相比&#xff0c;re_path 定义的路由可以使用正则表达式匹配url。 需要注意的是&#xff1a; 如果未定义匹配结果的变量名&#xff0c;匹配的结果默认传入视图的第2个形参。如果定义了匹配结果的变量名&#xff0c;匹配的结果会传给视图的同名字段&#xff0…

【GitOps系列】从零上手GitOps

文章目录 GitOps 介绍如何将业务代码构建为容器镜像&#xff1f;如何将容器镜像部署到K8s&#xff1f;K8s如何实现自动扩容和自愈&#xff1f;1.传统的扩容和自愈2.k8s自愈机制3.k8s弹性扩容 如何借助GitOps实现应用秒级自动发布和回滚&#xff1f;1.传统 K8s 应用发布流程2.从…

【Arduino小车实践】陀螺仪的使用

一、MPU6050简介 MPU6050是一款陀螺仪模块&#xff0c;可以测量X、Y、Z三轴的角速度和加速度&#xff0c;还带有温度传感器和数字运动处理器(DMP)。 二、学习步骤 1. I2C协议 MPU6050是通过I2C协议进行驱动的&#xff0c;配置寄存器和获取数据都需要通过I2C协议去实现开发板与…

CentOS环境下的Nginx安装

Nginx 安装 下载 nginx 下载地址&#xff1a;http://nginx.org/en/download.html 将下载好的压缩包拷贝到根目录下 通过xshell如果出现 bash: rz: 未找到命令 &#xff0c;需要先运行下面的命令 yum -y install lrzsz安装 解压到当前目录 tar -zxvf nginx-1.22.1.tar.gz安…

Hive SQL 迁移 Flink SQL 在快手的实践

摘要&#xff1a;本文整理自快手数据架构工程师张芒&#xff0c;阿里云工程师刘大龙&#xff0c;在 Flink Forward Asia 2022 生产实践专场的分享。本篇内容主要分为四个部分&#xff1a; Flink 流批一体引擎 Flink Batch 生产实践 核心优化解读 未来规划 点击查看原文视频…

走进人工智能| Computer Vision 数字化时代的视觉启示录

前言&#xff1a; 计算机视觉是通过模仿人类视觉系统的工作原理&#xff0c;使计算机能够感知、理解和解释图像和视频的能力。 文章目录 序言背景适用领域技术支持应用领域程序员如何学总结 序言 计算机视觉是人工智能领域的一个重要分支&#xff0c;它涉及使计算机能够“看”…

靶场的安装

sqli-lab 1.将安装包解压放到WWW目录下 2.修改 db-creds.inc文件里面的数据库的用户名密码为自己的用户名密码 路径&#xff1a;D:\phpStudy_64\phpstudy_pro\WWW\sqli-labs-master\sql-connections\db-creds.inc 3. 更改php版本位5.9版本&#xff0c;不然会报错 4.安装数…

MFC学习之2048小游戏程序源码

2048游戏的开发原理相对简单&#xff0c;它基于一个4x4的方格&#xff0c;通过控制数字方块的移动来合成相同的数字方块&#xff0c;并生成新的数字方块。 具体实现过程如下&#xff1a; 确定需求&#xff1a;首先需要明确游戏的功能需求&#xff0c;如产生随机数字方块、控制…

MYSQL执行一条SELECT语句的具体流程

昨天CSDN突然抽风 我一个ctrlz把整篇文章给撤掉了还不能复原 直接心态崩了不想写了 不过这部分果然还是很重要,还是写出来吧 流程图 这里面总共有两层结构Server层 储存引擎 Server 层负责建立连接、分析和执行 SQL。MySQL 大多数的核心功能模块都在这实现&#xff0c;主要包…

WebSocket理解

WebSocket理解 WebSocket定义与HTTP关系相同点:不同点&#xff1a;联系总体过程 HTTP问题长轮询Ajax轮询 WebSocket特点 WebSocket 定义 本质上是TCP的协议 持久化的协议 实现了浏览器和服务器的全双工通信&#xff0c;能更好的节省服务器资源和带宽 与HTTP关系 相同点: 基于…

接口测试 react+unittest+flask 接口自动化测试平台

目录 1 前言 2 框架 2-1 框架简介 2-2 框架介绍 2-3 框架结构 3 平台 3-1 平台组件图 1 新建用例 2 生成测试任务 3 执行并查看测试报告 3-2 用例管理 3-2-1 用例设计 3-3 任务管理 3-3-1 创建任务 3-3-2 执行任务 3-3-3 测试报告 3-3-4 邮件通知 1 前言 构建…

【力扣算法12】之 11. 盛最多水的容器 python

文章目录 问题描述示例1示例2提示 思路分析代码分析完整代码详细分析运行效果截图调用示例运行结果完结 问题描述 给定一个长度为 n 的整数数组 height 。有n条垂线&#xff0c;第i条线的两个端点是(i, 0)和(i, height[i])。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构…

解决IDEA项目external libraries依赖包消失的问题

有时候电脑重启后&#xff0c;再打开IDEA上的项目时会出现external libraries目录下的依赖包都消失了的情况&#xff0c;只剩下了一个JDK的包 网上说可以通过刷新IDEA的缓存解决&#xff0c;但我试了没有效果&#xff0c;最后使用如下办法解决&#xff1a; 1.删除项目目录下的…

图论算法笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 第12章 最短路径算法12-1 有权图的最短路径问题最短路径问题-路径规划单源最短路径带权图的最短路径和无权图的最短路径带权图的最短路径算法-Dijkstra算法 12-2 Di…