一文总结代理:代理模式、代理服务器

news2024/11/17 12:33:53

概述

代理在计算机编程领域,是一个很通用的概念,包括:代理设计模式,代理服务器等。

代理类持有具体实现类的实例,将在代理类上的操作转化为实例上方法的调用。为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可以去掉功能服务或者增加额外的功能。

角色

代理模式一般涉及到的角色有:

  • 对象:Client,请求客户端
  • 抽象角色:Subject,声明真实对象和代理对象的共同接口,对应代理接口;
  • 真实角色:RealSubject,代理角色所代表的真实对象,是最终要引用的对象,对应委托类;
  • 代理角色:Proxy,代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装,对应代理类,可以有未公开的方法。

UML类图如下:
在这里插入图片描述
调用顺序示意图:
在这里插入图片描述

分类

代理从类型来分类:

  • 虚代理:虚拟代理,Virtual Proxy,根据需要来创建开销很大的对象,该对象只有在需要时才会被真正创建,即所谓的延迟加载;
  • 远程代理:Remote Proxy,用来在不同的地址空间上代表同一个对象,这个不同的地址空间可以是在本机,也可以在其它机器上,在Java里面最典型的就是RMI技术;
  • copy-on-write代理:在客户端操作时,只有对象确实改变后,才会真的拷贝一个日标对象,算是虚代理的一个分支;
  • 保护代理:Protect Proxy,控制对原始对象的访问,如果有需要,可以给不同的用户提供不同的访问权限,以控制他们对原始对象的访问;
  • Cache代理:为那些昂贵操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果;
  • 防火墙代理:保护对象不被恶意用户访问和操作;
  • 同步代理:使多个用户能够同时访问目标对象而没有冲突;
  • 智能引用代理:Smart Reference Proxy,在访问对象时执行一些附加操作。比如,对指向实际对象的引用计数、第一次引用一个持久对象时,将它装入内存等

另外,根据代理类的生成时间的不同,可分为静态代理和动态代理。

静态代理

代理和被代理对象(目标对象)在代理之前是确定的,都实现相同的接口或者继承相同的抽象类。目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态,在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定。

实现静态代理有继承、聚合方法。

优点:业务类只需要关注业务逻辑本身,保证业务类的重用性。

缺点:

  1. 需要大量硬编码
  2. 一个代理类只能代理一个业务类
  3. 如果业务类增加方法时,相应的代理类也要增加方法

动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。优势在于可以很方便的对代理类的函数进行统一的处理,添加方法调用次数、添加日志功能等,而不用修改每个代理类的函数。分为JDK动态代理和cglib动态代理。

实现

一般有JDK和cglib等实现方式。

JDK

基于反射,核心API包括:
java.lang.reflect.Proxy,Java动态代理机制生成的所有动态代理类的父类,提供一组静态方法来为一组接口动态地生成代理类及其对象,共4个static方法:

public class Proxy implements java.io.Serializable {
	// 用于获取指定代理对象所关联的调用处理器
	public static InvocationHandler getInvocationHandler(Object proxy)
	// 用于获取关联于指定类装载器和一组接口的动态代理类的类对象
	public static class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
	// 用于判断指定类对象是否是一个动态代理类
	public static boolean isProxyClass(Class<?> cl)
	// 用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
	public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
}

InvocationHandler接口主要用来处理执行逻辑,源码:

public interface InvocationHandler {
	// obj指代理类,method指被代理的方法,args为该参数的方法
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

使用JDK动态代理的步骤

  1. 创建被代理的类以及接口;
  2. 创建一个实现接口InvocationHandler,并重写invoke方法的类;
  3. 调用Proxy的静态方法,创建代理类newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
  4. 通过代理调用方法。

实例

代表Subject的接口:

public interface Movable {
	void move();
}

Car实现Movable接口,是要代理的实际对象,对应RealSubject:

@Slf4j
public class Car implements Movable {
	@Override
	public void move() {
		// 实现开车
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中....");
		} catch (InterruptedException e) {
			log.error("thread fail", e);
		}
	}
}

对应于Proxy的TimeHandler类实现InvocationHandler接口,并在invoke()方法中添加额外的逻辑,用于在代理对象方法调用前后执行:

public class TimeHandler implements InvocationHandler {
	private final Object target;
	
	TimeHandler(Object target) {
		super();
		this.target = target;
	}
	
	/**
	 * 参数:
	 * proxy:被代理对象
	 * method:被代理对象的方法
	 * args:方法的参数
	 * res:方法的返回值
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		long startTime = System.currentTimeMillis();
		System.out.println("汽车开始行驶....");
		Object res = method.invoke(target, args);
		long endTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶....  汽车行驶时间:" + (endTime - startTime) + "毫秒!");
		return res;
	}
}

JDK动态代理测试类,也就是上图中的Client类:

public class Test {
	public static void main(String[] args) {
		Car car = new Car();
		InvocationHandler h = new TimeHandler(car);
		Class<?> cls = car.getClass();
		/*
		 * loader:类加载器
		 * interfaces:实现接口
		 * h:InvocationHandler
		 */
		Movable m = (Movable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
		m.move();
	}
}

使用Proxy.newProxyInstance()方法创建代理对象,指定Movable接口作为代理对象类型,并将TimeHandler对象作为代理对象的InvocationHandler。

缺点:

  1. 仍有硬编码
  2. 需要在对象初始化时,使用特定的方式进行初始化

源码分析

基于JDK-22,以Proxy.newProxyInstance()方法为切入点:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
	Objects.requireNonNull(h);
	@SuppressWarnings("removal")
	final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass();
	// 查找或生成指定的代理类及其构造函数
	Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
	return newProxyInstance(caller, cons, h);
}

基于源码的注释,进一步查看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());
	}
}

核心方法是构建ProxyBuilder实例,ProxyBuilder是java.lang.reflect.Proxy的静态内部类,利用构造器模式:

ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
	// 在模块系统完全初始化之前不支持代理
	if (!VM.isModuleSystemInited()) {
	    throw new InternalError("Proxy is not supported until module system is fully initialized");
	}
	// 接口数不得超过65535个
	if (interfaces.size() > 65535) {
		throw new IllegalArgumentException("interface limit exceeded: " interfaces.size());
	}
	Set<Class<?>> refTypes = referencedTypes(loader, interfaces);
	// IAE if violates any restrictions specified in newProxyInstance
	validateProxyInterfaces(loader, interfaces, refTypes);
	this.interfaces = interfaces;
	this.context = proxyClassContext(loader, interfaces, refTypes);
	assert getLoader(context.module()) == loader;
}

核心方法之一,referencedTypes检查是否为static方法:

private static Set<Class<?>> referencedTypes(ClassLoader loader, List<Class<?>> interfaces) {
	var types = new HashSet<Class<?>>();
	for (var intf : interfaces) {
		for (Method m : intf.getMethods()) {
			// 不能为static方法
			if (!Modifier.isStatic(m.getModifiers())) {
				addElementType(types, m.getReturnType());
				addElementTypes(types, m.getSharedParameterTypes());
				addElementTypes(types, m.getSharedExceptionTypes());
			}
		}
	}
	return types;
}

validateProxyInterfaces方法检查是否是接口,

private static void validateProxyInterfaces(ClassLoader loader, List<Class<?>> interfaces, Set<Class<?>> refTypes) {
	Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.size());
	for (Class<?> intf : interfaces) {
	    // 检查是否为接口
	    if (!intf.isInterface()) {
	        throw new IllegalArgumentException(intf.getName() + " is not an interface");
	    }
		// 检查是否为隐藏类:JDK15引入新特性
	    if (intf.isHidden()) {
	        throw new IllegalArgumentException(intf.getName() + " is a hidden interface");
	    }
		// 检查是否是密封类:JDK17引入新特性
	    if (intf.isSealed()) {
	        throw new IllegalArgumentException(intf.getName() + " is a sealed interface");
	    }
	    // 验证类加载器是否将此接口的名称解析为同一个Class对象,下同
	    ensureVisible(loader, intf);
	    // 检查是否已经生成过此接口的代理类
	    if (interfaceSet.put(intf, Boolean.TRUE) != null) {
	        throw new IllegalArgumentException("repeated interface: " + intf.getName());
	    }
	}
	for (Class<?> type : refTypes) {
	    ensureVisible(loader, type);
	}
}

为什么JDK动态代理只能代理接口

面试时常见的问题之一,参考上面的源码分析,实际上还可以补充回答:JDK动态代理对密封类,隐藏类不生效,即不能代理密封类,隐藏类。

cglib

Code Generation Library,一款高性能Code生成类库的开源组件,可在运行期扩展Java类与实现Java接口,很多其他开源组件都在使用cglib

net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调类型,经常被基于代理的AOP用来实现拦截方法的调用,接口只定义一个方法:

public interface MethodInterceptor extends Callback {
	// object是代理对像,method是拦截方法,args是方法参数。原来的方法可通过使用Method对象的一般反射调用,或使用MethodProxy对象调用,后者更快
	Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}	

实例

创建代理类:

public class CglibProxy implements MethodInterceptor {
	private final Enhancer enhancer = new Enhancer();
	
	Object getProxy(Class<?> clazz) {
	    // 设置创建子类的类
	    enhancer.setSuperclass(clazz);
	    enhancer.setCallback(this);
	    return enhancer.create();
	}
	
	/**
	 * 拦截所有目标类方法的调用
	 */
	@Override
	public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable {
	    System.out.println("日志开始...");
	    // 代理类调用父类的方法
	    Object res = proxy.invokeSuper(obj, args);
	    System.out.println("日志结束...");
	    return res;
	}
}

测试类:

public class Client {
	
	public static void main(String[] args) {
		CglibProxy proxy = new CglibProxy();
		Train t = (Train) proxy.getProxy(Train.class);
		t.move();
	}
	
	static class Train {
		void move() {
			System.out.println("火车行驶中...");
		}
	}
}

源码分析

基于cglib最新版3.3.0来分析,从enhancer.create()入手,调用createHelper方法:

private Object createHelper() {
	// 校验callbackTypes、filter是否为空,及相应处理策略
	preValidate();
	Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
	        ReflectUtils.getNames(interfaces),
	        filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
	        callbackTypes,
	        useFactory,
	        interceptDuringConstruction,
	        serialVersionUID);
	this.currentKey = key;
	Object result = super.create(key);
	return result;
}

核心方法是create:

protected Object create(Object key) {
	try {
		ClassLoader loader = getClassLoader();
		// 查询缓存
		Map<ClassLoader, ClassLoaderData> cache = CACHE;
		ClassLoaderData data = cache.get(loader);
		// DCL
		if (data == null) {
			synchronized (AbstractClassGenerator.class) {
				cache = CACHE;
				data = cache.get(loader);
				if (data == null) {
					// 构建缓存
					Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
					data = new ClassLoaderData(loader);
					newCache.put(loader, data);
					CACHE = newCache;
				}
			}
		}
		this.key = key;
		Object obj = data.get(this, getUseCache());
		if (obj instanceof Class) {
			// 用于向后兼容
			return firstInstance((Class) obj);
		}
		// 真正创建代理对象
		return nextInstance(obj);
	} catch (RuntimeException e) {
		throw e;
	} catch (Error e) {
		throw e;
	} catch (Exception e) {
		throw new CodeGenerationException(e);
	}
}

不管是firstInstance还是nextInstance,最后都是调用ReflectUtils.newInstance方法:

public static Object newInstance(final Constructor cstruct, final Object[] args) {
	boolean flag = cstruct.isAccessible();
	try {
	    if (!flag) {
	        cstruct.setAccessible(true);
	    }
	    // 使用JDK
	    return cstruct.newInstance(args);
	} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
	    throw new CodeGenerationException(e);
	} finally {
	    if (!flag) {
	        cstruct.setAccessible(flag);
	    }
	}
}

最后使用JDK源码Constructor.newInstance(args);,因此cglib不能对声明为final的方法进行代理,因为cglib原理是动态生成被代理类的子类。

区别

主要区别:

  • JDK:利用拦截器(实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理;
  • cglib:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

具体来说:
JDK动态代理只能针对实现接口的类生成代理(实例化一个类)。此时代理对象和目标对象实现相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。

cglib是针对类实现代理,主要是对指定的目标类生成一个子类(没有实例化一个类),覆盖其中的方法,通过方法拦截技术拦截所有父类方法的调用。

使用区别:

  • JDK不能用于非接口类、隐藏类、(未经允许扩展的)密封类(的子类)
  • cglib不能用于final方法、隐藏类、同上

Spring AOP

Spring AOP基于JDK Proxy和cglib来生成代理对象,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认策略是如果目标类是接口,则使用JDK动态代理,如果目标对象没有实现接口,则默认会采用cglib代理。

代理模式将继承模式和关联模式结合在一起使用,是两者的综合体,不过这个综合体的作用倒不是解决对象注入的问题,而是为具体操作对象找到一个保姆或者是秘书,对外代表具体的实例对象,实例对象的入口和出口都是通过这个二号首长,具体的实例对象是一号首长,一号首长是要干大事的,所以一些事务性,重复性的工作例如泡茶,安排车子,这样的工作是不用劳烦一号首长的大驾,而是二号首长帮忙解决的,这就是AOP的思想。AOP解决程序开发里事务性,和核心业务无关的问题,但这些问题对于业务场景的实现是很有必要的,在实际开发里AOP也是节省代码的一种方式。

AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,大中型应用都会涉及到的持久化、事务、安全、日志和调试等。

实现AOP的技术,主要分为两大类:

  • 动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
  • 静态织入的方式,引入特定的语法创建Aspect,从而使得编译器可以在编译期间织入有关Aspect代码。

拓展

代理服务器

一般有正向代理、反向代理。

正向代理

一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。如通过Chrome的SwitchSharp访问外网。

反向代理

反向代理:以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。这个服务器没有保存任何网页的真实数据,所有的静态网页或者CGI程序,都保存在内部的Web服务器上。因此对反向代理服务器的攻击并不会使得网页信息遭到破坏,这样就增强Web服务器的安全性。

反向代理经常和CDN一起工作,其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置反向代理节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决Internet网络拥挤的状况,提高用户访问网站的响应速度。

区别

正向代理解决的是客户端访问互联网的问题,客户端知道目标的;
反向代理解决的是互联网收到客户端请求,如何把请求转到内网服务器的问题,不知道目标,代理的是服务端。

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

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

相关文章

Haproxy 下载、编译部署、使用

文章目录 前言Haproxy 下载、编译部署、使用1. 下载2. 编译部署3. 使用3.1. 验证配置文件3.2. 启动 HAProxy 并指定配置文件路径3.3. 关闭HAProxy3.4. 重载HAProxy 3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&am…

go-kratos 学习笔记(7) 服务发现服务间通信grpc调用

服务发现 Registry 接口分为两个&#xff0c;Registrar 为实例注册和反注册&#xff0c;Discovery 为服务实例列表获取 创建一个 Discoverer 服务间的通信使用的grpc&#xff0c;放到data层&#xff0c;实现的是从uses服务调用orders服务 app/users/internal/data.go 加入 New…

可见性::

目录 定义&#xff1a; 解决方法&#xff1a; ①使用synchronized实现缓存和内存的同步 修改一&#xff1a; 加入语句&#xff1a; 代码&#xff1a; 修改2&#xff1a; 在代码块中加入&#xff1a; 代码&#xff1a; 执行结果&#xff1a; 原因&#xff1a; ②使用…

数字经济赋能爱疆事业:同疆共创,打造疆品出疆新通道

在数字化浪潮的推动下&#xff0c;新疆吐鲁番地区正迎来一场前所未有的变革。近日&#xff0c;由同疆共创公司承办的“品牌新力量&#xff0c;助农电商直播行&#xff0c;音乐嘉年华吐鲁番活动”在吐鲁番市火热开展&#xff0c;这一创新举措不仅彰显了同疆共创积极履行社会责任…

【redis】一致性hash算法和hash槽

普通hash取模 直接hash(key)%N , N为机器的数量&#xff0c;但不利于集器扩容或者缩容 一致性hash算法和hash槽 一致性hash算法是在redis 分片中使用&#xff0c;hash槽在redis cluster&#xff08;集群&#xff09;中使用 Redis一致性hash&#xff1a;Redis一致性hash是为…

AvaloniaUI的学习

相关网站 github:https://github.com/AvaloniaUI/Avalonia 官方中文文档&#xff1a;https://docs.avaloniaui.net/zh-Hans/docs/welcome IDE选择 VS2022VSCodeRider 以上三种我都尝试过&#xff0c;体验Rider最好。VS2022的提示功能不好&#xff0c;VSCode太慢&#xff0c…

Typora笔记上传到CSDN

1.Typora 安装 Typora链接&#xff1a;百度网盘 提取码&#xff1a;b6d1 旧版本是不需要破解的 后来的版本比如1.5.9把放在typora的根目录下就可以了 2.上传到CSDN 步骤 csdn 写文章-使用MD编辑器-导入本地md文件即可 问题 图片没法显示 原因 图片的链接是本地的 当然没法…

PySide(PyQt)使用QPropertyAnimation制作动态界面

主脚本&#xff1a; # encoding: utf-8 import os import sysfrom PySide6.QtCore import QPropertyAnimation, QEasingCurvefrom UIS import *# 主画面类 class MainWindow(QMainWindow, animationButton_ui.Ui_MainWindow):def __init__(self):super().__init__()self.setup…

【OpenCV C++20 学习笔记】图片处理基础

OpenCV C20 图片处理基础 VS 2022 C20 标准库导入的问题头文件包含以及命名空间声明main函数读取图片读取检查显式图片写入图片 完整代码bug VS 2022 C20 标准库导入的问题 VS还没有完全兼容C20。C20的import语句不一定能正确导入标准库&#xff0c;所以必须要新建一个头文件专…

基站光伏直流叠光能效管理方案

安科瑞 华楠 基站现状和趋势 5G基站是专门提供5G网络服务的公用移动通信基站。5G基站主要用于提供5G空口协议功能&#xff0c;支持与用户设备、核心网之间的通信。按照逻辑功能划分&#xff0c;5G基站可分为5G基带单元与5G射频单元&#xff0c;二者之间可通过CPRI或eCPRI接口…

Flink 技术与应用(一)

Flink技术与应用&#xff08;初级篇&#xff09; 起源 Apache Flink 是一个开源的大数据处理框架&#xff0c;其起源可以追溯到一个名为 Stratosphere 的研究项目&#xff0c;旨在建立下一代大数据分析引擎&#xff0c;2010 年&#xff0c;从 Stratosphere 项目中分化出了 Fl…

基于深度学习算法,支持再学习功能,不断提升系统精准度的智慧地产开源了。

智慧地产视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。通过计算机视觉和…

OpenCV图像滤波(2)均值平滑处理函数blur()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在OpenCV中&#xff0c;blur()函数用于对图像应用简单的均值模糊&#xff08;mean blur&#xff09;。这种模糊效果可以通过将图像中的每个像素替…

【OpenCV C++20 学习笔记】调节图片对比度和亮度(像素变换)

调节图片对比度和亮度&#xff08;像素变换&#xff09; 原理像素变换亮度和对比度调整 代码实现更简便的方法结果展示 γ \gamma γ校正及其实操案例线性变换的缺点 γ \gamma γ校正低曝光图片矫正案例代码实现 原理 关于OpenCV的配置和基础用法&#xff0c;请参阅本专栏的其…

项目管理中的常用工件(二):可视化工件

项目管理中的常用工件&#xff08;二&#xff09;&#xff1a;可视化工件 亲和图&#xff08;affinity diagram&#xff09;因果图&#xff08;cause-and-effect diagram&#xff09;直方图&#xff08;histogram&#xff09;流程图&#xff08;flowchart&#xff09;散点图&am…

Golang学习笔记20240725,Go语言基础语法

第一个Go程序 package mainimport "fmt"func main() {fmt.Println("hello world") }运行方式1&#xff1a; go run main.go运行方式2&#xff1a; go build .\hello_go.exe运行方式3&#xff1a;goland右键运行 字符串拼接 使用加号可以对字符串进行…

数据结构初阶 · 二叉搜索树

目录 前言: 二叉搜索树的实现 二叉搜索树的基本结构 增 查 中序遍历 删 前言: 在最初学习二叉树的时候&#xff0c;就提及到过单独用树来存储数据是既不如链表也不如顺序表的&#xff0c;二叉树的用处可以用来排序&#xff0c;比如堆排序&#xff0c;也可以用来搜索数据…

雷军的逆天改命与顺势而为

雷军年度演讲前&#xff0c;朋友李翔提了一个问题&#xff1a;雷军造车是属于顺势而为还是逆势而为&#xff1f;评论互动区有一个总结&#xff0c;很有意思&#xff0c;叫“顺势逆袭”。 大致意思是产业趋势下小米从手机到IOT再切入汽车&#xff0c;是战略的必然&#xff0c;不…

学习Java的日子 Day58 Servlet的生命周期,安全问题,页面跳转,中文乱码问题

Day58 1.Servlet的生命周期 创建&#xff1a;第一次发送给该Servlet请求时 ​ 调用&#xff1a;构造方法、init() 销毁&#xff1a;服务器正常关闭 ​ 调用&#xff1a;destroy() Welcome.html 没有明确写出是什么请求&#xff0c;那就是get请求 <!DOCTYPE html> <ht…

JavaWeb笔记_JSTL标签库JavaEE三层架构案例

一.JSTL标签库 1.1 JSTL概述 JSTL(jsp standard tag library):JSP标准标签库,它是针对EL表达式一个扩展,通过JSTL标签库与EL表达式结合可以完成更强大的功能 JSTL它是一种标签语言,JSTL不是JSP内置标签 JSTL标签库主要包含: ****核心标签 格式化标签 …