代理模式详解、RESTFul风格、Spring IOC

Day49

代理模式proxy

概念: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

代理模式分为静态代理和动态代理两种 。

静态代理

思路:对于同一个接口,代理类和被代理类都实现了这个接口,代理类中将被代理类对象持有为自己的属性,这样在使用方法的时候就可以在被代理类的方法前后加上增强,即自己的方法逻辑。

以Speaker接口、ChineseSpeaker、AmericaSpeaker、ChinsesSpeakerProxy、AmericaSpeakerProxy为例,其中ChinsesSpeakerProxy、AmericaSpeakerProxy分别是ChineseSpeaker、AmericaSpeaker的代理类:

Speaker:

public interface Speaker {void speak();
}

ChineseSpeaker:

public class ChineseSpeaker implements Speaker{public void speak() {System.out.println("中文演讲");}
}

ChinsesSpeakerProxy:

public class ChineseSpeakerProxy implements Speaker{private ChineseSpeaker speaker;public ChineseSpeakerProxy(ChineseSpeaker speaker) {this.speaker = speaker;}@Overridepublic void speak() {System.out.println("增强处理");speaker.speak();System.out.println("增强处理");}
}

可以看出,这种写法每代理一个真实类就需要写一个代理类,对于AmericaSpeaker,同样要写一个AmericaSpeakerProxy。如果对于功能增强的内容完全相同,就可以使用一个对于Speaker接口通用的代理类CommonSpeakerProxy,利用多态完成代理。

public class CommonSpeakerProxy implements Speaker {private Speaker speaker;public CommonSpeakerProxy(Speaker speaker) {this.speaker = speaker;}@Overridepublic void speak() {System.out.println("前置功能增强");speaker.speak();System.out.println("后置功能增强");}
}

除了这种写法外,还可以利用反射的思想来写,利用多态通过接口实现类拿到方法,通过有参传入的类对象,用method.invoke()方法完成代理。

假设这时再添加了Seller接口、ChineseSeller、AmericaSeller类及其代理类CommonSellerProxy

public class CommonSellerProxy implements Seller{private static Method method;private  Object seller;public CommonSellerProxy(Object seller){this.seller = seller;}static {try {method = Seller.class.getMethod("sell");} catch (NoSuchMethodException e) {e.printStackTrace();}}@Overridepublic void sell() {try {System.out.println("前置功能增强");method.invoke(seller);System.out.println("后置功能增强");} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}}
}

可以看出,静态代理每实现一个真实类的代理就需要写一个代理类,如果代理的功能不同,就需要针对代理功能编写多个类,十分复杂。那么有没有一种方式使得在使用到代理的时候再去编写代理的逻辑功能,而不是每次都去多写一个类呢?这就是动态代理的思想。

动态代理

动态代理又根据代理对象进行划分:

为接口做代理:JDK动态代理

为类做代理:CGLIB动态代理

JDK动态代理

由于动态代理和静态代理差别较大,这里从静态代理开始进阶优化,直到达到动态代理的范畴。

**静态代理进阶:**思路:写一个接口,接口中定义了代理重写的方法,在代理类中以匿名内部类的方式创建一个类对象作为自己持有的属性,并用全参构造要求使用时创建这个匿名内部类(即重写代理方法内容),而代理类中就只需要调用接口的实现类的方法就行,不需要再写明方法逻辑。

这个接口是jdk自带的接口,在这里自己写一遍,以更好地明白逻辑:

MethodInvocationHandler接口:

public interface MethodInvocationHandler {Object handle(Object target, Method method,Object[] args) throws Exception;
}

注意:这个方法的本质是反射,利用method.invoke()方法进行调用真实类的方法,再加上代理类的方法,因此参数为method.invoke()的参数。

ChineseSpeakerProxy代理类:

public class ChineseSpeakerProxy implements Speaker{private Speaker speaker;private MethodInvocationHandler handler;private static Method method;static {try {method = Speaker.class.getMethod("speak");} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}public ChineseSpeakerProxy(Speaker speaker, MethodInvocationHandler handler) {this.speaker = speaker;this.handler = handler;}//如果有多个需要代理的方法就都要进行重写@Overridepublic void speak() {try {handler.handle(speaker,method,null);//由于真实类中的方法是无参的,所以这里的参数数组为空} catch (Exception e) {throw new RuntimeException(e);}}
}

使用:

public class Test01 {public static void main(String[] args) {ChineseSpeaker chineseSpeaker = new ChineseSpeaker();ChineseSpeakerProxy chineseSpeakerProxy = new ChineseSpeakerProxy(chineseSpeaker, new MethodInvocationHandler() {@Overridepublic Object handle(Object target, Method method, Object[] args) throws Exception {System.out.println("功能增强");method.invoke(target,args);System.out.println("功能增强");return null;}});chineseSpeakerProxy.speak();}

至此,对于同一个代理类的多个功能,实现了让用户自己写增强方法的目的。但是对于一个真实类,如果要实现其代理,那还是要写一个代理类,如果真实类很多,那就需要写很多的代理类,代理繁多的问题依然存在。

如果这些代理类能够使用代码来生成,然后再编译,再加载至 JVM 中,那么再多的代理也就不是问题了。

动态代理

手动写一个能够自动创建代理类源码的类,然后手动完成编译、加载的过程。(这些功能jdk的接口都实现了,这里只手写一个自动创建代理类源码的类以便深刻理解)

因此,可以手动写一个能够自动创建代理类源码的类,然后手动完成编译、加载的过程。(这些功能jdk的接口都实现了,这里只手写一个自动创建代理类源码的类以便深刻理解)

package com.qf.proxy;import com.qf.proxy.dynamic.MethodInvocationHandler;import java.lang.reflect.Method;
import java.lang.reflect.Parameter;public class MyProxy {private static String generateProxyClass(Class<?> clazz){if(!clazz.isInterface()) throw new IllegalArgumentException(clazz.getName() + " 不是接口");StringBuilder builder = new StringBuilder();builder.append("package ").append(clazz.getPackage().getName()).append(";\n");builder.append("import ").append(Method.class.getName()).append(";\n");builder.append("import ").append(MethodInvocationHandler.class.getName()).append(";\n");builder.append("import ").append(MyProxy.class.getName()).append(";\n");builder.append("public class $proxy0 extends MyProxy implements ").append(clazz.getSimpleName()).append("{\n");StringBuilder staticBuilder = new StringBuilder();staticBuilder.append("static {\n");staticBuilder.append("try {\n");StringBuilder overrideMethodBuilder = new StringBuilder();Method[] methods = clazz.getMethods();for(int i=0; i<methods.length; i++){builder.append("private static Method m").append(i).append(";\n");staticBuilder.append("m").append(i).append("=Class.forName(\"").append(clazz.getName()).append("\").getMethod(\"").append(methods[i].getName()).append("\",");overrideMethodBuilder.append("\n@Override\n");overrideMethodBuilder.append("public ").append(methods[i].getReturnType().getSimpleName()).append(" ").append(methods[i].getName()).append("(");Parameter[] parameters = methods[i].getParameters();for(Parameter parameter : parameters){staticBuilder.append(parameter.getType().getSimpleName()).append(".class,");overrideMethodBuilder.append(parameter.getType().getSimpleName()).append(" ").append(parameter.getName()).append(",");}staticBuilder.deleteCharAt(staticBuilder.length()-1);staticBuilder.append(");\n");if(parameters.length > 0)overrideMethodBuilder.deleteCharAt(overrideMethodBuilder.length()-1);overrideMethodBuilder.append("){\n");Class returnType = methods[i].getReturnType();if(returnType != Void.class && returnType != void.class)overrideMethodBuilder.append("return (").append(methods[i].getReturnType().getSimpleName()).append(")");overrideMethodBuilder.append("handler.handle(m").append(i).append(",new Object[]{");for(Parameter parameter : parameters){overrideMethodBuilder.append(parameter.getName()).append(",");}if(parameters.length > 0)overrideMethodBuilder.deleteCharAt(overrideMethodBuilder.length()-1);overrideMethodBuilder.append("});\n}");}staticBuilder.append("} catch (NoSuchMethodException e) {\ne.printStackTrace();\n}catch (ClassNotFoundException e) {\ne.printStackTrace();\n}\n");staticBuilder.append("}\n");builder.append(staticBuilder);builder.append("protected $proxy0(MethodInvocationHandler handler) {\nsuper(handler);\n}\n");builder.append(overrideMethodBuilder);builder.append("\n}");System.out.println(builder);return builder.toString();}//    public static void main(String[] args) {
//        generateProxyClass(Seller.class);
//    }
}

这个类完成了自动创建类源码的功能,其实现的思路就是将一个代理类中的特定类利用反射和object去替换,然后将整个类写成字符串放进StringBuilder中。

然后是编译代理类源文件、加载编译好的代理类(利用类加载器)、编写创建代理实例的方法。这些底层就不手写了。

接下来对比一下两种写法的区别(这里都实现jdk自带的InvocationHandler接口,接口内容和上面手写的MethodInvocationHandler一致,只是方法名字为invoke,我写的是handle):

手动创建ChineseSpeakerProxy(此时仍然为静态代理,因为代理类是在编译时明确定义的,并且代理类的代码是手动编写的。相对于动态代理,静态代理类在运行时不会自动生成,而是在编译时就已经存在。 ):

public class ChineseSpeakerProxy implements Speaker{private Speaker speaker;private InvocationHandler handler;private static Method method;static {try {method = Speaker.class.getMethod("speak");} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}public ChineseSpeakerProxy(Speaker speaker, InvocationHandler handler) {this.speaker = speaker;this.handler = handler;}@Overridepublic void speak() {try {handler.invoke(speaker,method,null);} catch (Throwable e) {throw new RuntimeException(e);}}
}public class Test01 {public static void main(String[] args) {ChineseSpeaker chineseSpeaker = new ChineseSpeaker();ChineseSpeakerProxy chineseSpeakerProxy = new ChineseSpeakerProxy(chineseSpeaker, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {method.invoke(proxy,args);System.out.println("方法增强");return null;}});chineseSpeakerProxy.speak();}

使用jdk自带的Proxy类静态方法创建(动态代理):

public class Test01 {public static void main(String[] args) {ChineseSpeaker chineseSpeaker = new ChineseSpeaker();Speaker ChineseSpeakerProxy = (Speaker) Proxy.newProxyInstance(ChineseSpeaker.class.getClassLoader(), ChineseSpeaker.class.getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法增强");method.invoke(chineseSpeaker,args);return null;}});ChineseSpeakerProxy.speak();}

注意:使用Proxy类实现动态代理的时候,method.invoke()的第一个参数不是形参给的,而是被代理的类对象! 因为使用 Proxy.newProxyInstance动态生成的代理类会把代理实例本身传递给 InvocationHandler.invoke方法的 proxy 参数。因此,如果在 invoke 方法中再次调用 method.invoke(proxy, args),就会导致递归调用形成死循环。

总结

静态代理

写法一:真实类和代理类实现同一个接口,代理类持有真实类对象作为属性,并在重写接口方法的时候调用其方法,再添加方法增强。

写法二:如果多个真实类需要进行的方法增强相同,则可以写一个通用的代理类,实现和写法一相同。

写法三:利用反射的思想,代理类持有真实类对象属性,并利用反射拿到方法,重写的时候用invoke(),并添加方法增强。

写法四(进阶):利用接口(这里自己写的是MethodInvocationHandler)定义重写的方法,在代理类中创建接口的实现类(匿名内部类),并通过全参构造让用户自己传入真实类对象和接口实现类(并重写代理方法),调用实现类的方法。

动态代理:自动创建代理类源码,然后完成编译、加载。 代理类在运行时根据目标对象和增强逻辑动态生成。 在使用的时候利用Proxy类的静态方法newProxyInstance()创建代理类,并 通过 InvocationHandler 接口的 invoke 方法在运行时拦截方法调用,执行增强逻辑。

RESTful风格

RESTful风格不是标准,但是在企业中经常使用RESTful风格来完成功能的开发。

REST = Representational State Transfer(表属性状态转移)

简单来说就是在编写Servlet的时候重写doGet,doPost,doPut,doDelete实现增查改删功能。

Spring IOC

Spring简介

Spring 是目前主流的 Java 开发框架,是 Java 世界最为成功的框架。其目的是用于简化企业级应用程序开发的难度和周期,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。

什么是框架? 框架是一个半成品,提供了基本的运行功能,但具体业务实现需要我们去编写。

Spring体系结构

在这里插入图片描述

IOC概念:

IOC全称为 Inverse Of Control,表示控制反转。指的是程序员使用硬编码创建的对象转为由Spring容器来创建,对于对象生命周期的控制交给Spring容器来管理。控制反转解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健

依赖注入

DI全称为Dependency Injection,表示依赖注入。指的是在Spring创建对象的同时,为其属性赋值

设值注入:

创建一个类:

@Data
public class Student {private String name;private String sex;private int age;private Date birthday;
}

在xml配置文件中利用设值注入创建对象:

常见数据类型:

<!--application.xml-->
<bean name="stu" class="com.qf.spring.ioc.model.Student"><property name="name" value="张三" /><property name="age" value="20" /><property name="sex" value="" /><!--这里需要注意:日期类型的默认格式yyyy/MM/dd--><property name="birthday" value="2021/10/10" />
</bean>

使用:

@Test
public void studentTest(){//应用上下文使用的是类路径下XML文档作为当前应用上下文ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");//从上下文中根据bean的名称或者ID获取bean对象Student stu = context.getBean("stu", Student.class);System.out.println(stu);
}

注意:这里的name虽然是user,但是并不是指的user这个类,而是指向set方法!(理解为去掉set后的名字)设值注入本质就是通过set方法为属性注入值。设值注入必须保证存在无参构造,否则将报错。

注入数组类型:

spring 提供了 array 标签来进行数组类型的属性值的注入。

@Data
public class Clazz {private int id;private String name;private Student[] students;
}
<bean name="clazz" class="com.qf.spring.ioc.model.Clazz"><property name="id" value="1" /><property name="name" value="张三" /><property name="students"><array><!--引用数据类型 可以使用bean标签创建bean对象注入值--><!--<bean class=""></bean>--><!--引用数据类型 可以使用ref标签引用bean对象注入值--><ref bean="s" /><ref bean="stu" /><!--常用数据类型 可以使用value标签直接注入值--><!-- <value></value>--></array></property>
</bean>

注入集合类型:

List集合

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {private String name;private String sex;private int age;private Date birthday;private List<Double> scores;
}
<bean name="stu" class="com.qf.spring.ioc.model.Student"><property name="name" value="张三" /><property name="age" value="20" /><property name="sex" value="" /><!--这里需要注意:日期类型的默认格式yyyy/MM/dd--><property name="birthday" value="2021/10/10" /><property name="scores"><list><value>80.0</value><value>90.0</value><value>81.0</value><value>82.0</value></list></property>
</bean><bean name="s" class="com.qf.spring.ioc.model.Student"><!--这里按照顺序为属性注入值--><constructor-arg index="0" value="李四" /><constructor-arg index="1" value="" /><constructor-arg index="2" value="22" /><constructor-arg index="3" value="2020/05/05" /><constructor-arg index="4"><list><value>80.0</value><value>90.0</value><value>81.0</value><value>82.0</value></list></constructor-arg>
</bean>
Set集合
@Data
public class Person {private String name;private Set<String> friendNames;
}
<bean name="p" class="com.qf.spring.ioc.model.Person"><property name="name" value="李刚" /><property name="friendNames"><set><value>李四</value><value>王五</value></set></property>
</bean>
注入Map
@Data
public class Person {private String name;private List<String> friendNames;private Map<String, Object> map;
}
<bean name="p" class="com.qf.spring.ioc.model.Person"><property name="name" value="李刚" /><property name="friendNames"><set><value>李四</value><value>王五</value></set></property><property name="map"><map><entry key="hobby" value="聊天" /><entry key="clazz" value-ref="clazz"/></map></property><property name="props"><props><prop key="desc">我很帅</prop><prop key="secret">我有两个女朋友</prop></props></property>
</bean>
注入Properties
@Data
public class Person {private String name;private List<String> friendNames;private Properties props;
}
<bean name="p" class="com.qf.spring.ioc.model.Person"><property name="name" value="李刚" /><property name="friendNames"><set><value>李四</value><value>王五</value></set></property><property name="props"><props><prop key="desc">我很帅</prop><prop key="secret">我有两个女朋友</prop></props></property>
</bean>
构造注入

构造注入指的是通过构造放入为属性注入值。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {private String name;private String sex;private int age;private Date birthday;
}
<!--application.xml-->
<bean name="s" class="com.qf.spring.ioc.model.Student"><!--这里按照顺序为属性注入值--><constructor-arg index="0" value="李四" /><constructor-arg index="1" value="" /><constructor-arg index="2" value="22" /><constructor-arg index="3" value="2020/05/05" />
</bean>
@Test
public void studentConstructorTest(){//应用上下文使用的是类路径下XML文档作为当前应用上下文ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");//从上下文中根据bean的名称或者ID获取bean对象Student stu = context.getBean("s", Student.class);System.out.println(stu);
}

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

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

相关文章

开源数据科学平台Anaconda简介

开源数据科学平台Anaconda简介 零、时光宝盒 最近&#xff0c;某金融行业女性选择以跳楼的形式结束自己的生命&#xff0c;这件不幸的事情成了热门话题&#xff0c;各种猜测的都有&#xff0c;有些人评论的话真的很过分。我想起前段时间看到的&#xff0c;有个女学生跳江&#…

【论文解读】AGENTLESS:揭开基于LLM的软件工程代理的神秘面纱,重塑软件工程自动化新基线

&#x1f4dc; 文献卡 英文题目: Agentless: Demystifying LLM-based Software Engineering Agents;作者: Chunqiu Steven Xia; Yinlin Deng; Soren Dunn; Lingming ZhangDOI: 10.48550/arXiv.2407.01489摘要翻译: 大型语言模型&#xff08;LLM&#xff09;的最新进展显著推进…

进程的控制-ps和kill命令

ps 查看进程信息 部分参数&#xff1a; a : 显示现行终端机下的所有程序&#xff0c;包括其他用户的程序 u: 以用户为主的格式来显示程序状况 x: 显示所有程序&#xff0c;不以 终端机来区分 kill 向指定的进程发送信号 kill 可将指定的信息送至程序。预设的信息为 SIG…

OpenAI突然停止中国API使用,出海SaaS产品如何化挑战为机遇?

2023年是AI爆发的年代&#xff0c;人工智能带来的信息裂变刷新了整个SaaS行业。在这个AI引领的时代&#xff0c;我们不应该单纯依赖工具本身&#xff0c;而是要理解如何将这些AI功能与行业相结合。 然而&#xff0c;上周OpenAI宣布禁止对中国提供API服务&#xff0c;有一些用户…

六、数据可视化—flask框架入门(爬虫及数据可视化)

六、数据可视化—flask框架入门&#xff08;爬虫及数据可视化&#xff09; 1&#xff0c;数据可视化简介2&#xff0c;flask&#xff08;1&#xff09;创建flask项目&#xff08;2&#xff09;开启debug模式&#xff08;3&#xff09;通过访问路径传递参数&#xff08;4&#x…

图的应用之最短路径

引入 应用 算法思想 Dijistra算法 用于解决单个顶点间的最短路径问题 将顶点看成两部分&#xff1a; 最短路径顶点集合A与尚未确定最短路径顶点集合B。 先将顶点按最短路径由小到大依次加入到A中&#xff0c;选择由源点到A中最短的顶点&#xff0c;并记录距离与顶点&#xf…

身体(body)的觉醒:如果你贪婪,给你整个宇宙都不够

佛&#xff0c;是一个梵文的汉语音译词&#xff0c;指觉醒者。 何谓觉醒&#xff1f;什么的觉醒&#xff1f;其实很简单&#xff0c;就是身体的觉醒。 佛的另一个名字&#xff0c;叫菩提&#xff0c;佛就是菩提&#xff0c;菩提老祖&#xff0c;就是佛祖。 一、body&#xff…

Lambda架构

1.Lambda架构对大数据处理系统的理解 Lambda架构由Storm的作者Nathan Marz提出&#xff0c;其设计目的在于提供一个能满足大数据系统关键特性的架构&#xff0c;包括高容错、低延迟、可扩展等。其整合离线计算与实时计算&#xff0c;融合不可变性、读写分离和复杂性隔离等原则&…

3.js - 裁剪平面(clipIntersection:交集、并集)

看图 代码 // ts-nocheck// 引入three.js import * as THREE from three// 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 导入lil.gui import { GUI } from three/examples/jsm/libs/lil-gui.module.min.js// 导入tween import …

深度解析Ubuntu版本升级:LTS版本升级指南

深度解析Ubuntu版本升级&#xff1a;Ubuntu版本生命周期及LTS版本升级指南 Ubuntu是全球最受欢迎的Linux发行版之一&#xff0c;其版本升级与维护策略直接影响了无数用户的开发和生产环境。Canonical公司为Ubuntu制定了明确的生命周期和发布节奏&#xff0c;使得社区、企业和开…

Spring AOP源码篇三之 xml配置

简单代码示例, 了解Spring AOP基于xml的基本用法 xml配置&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-insta…

django之url路径

方式一&#xff1a;path 语法&#xff1a;<<转换器类型:自定义>> 作用&#xff1a;若转换器类型匹配到对应类型的数据&#xff0c;则将数据按照关键字传参的方式传递给视图函数 类型&#xff1a; str: 匹配除了”/“之外的非空字符串。 /test/zvxint: 匹配0或任何…

golang线程池ants-实现架构

1、总体架构 ants协程池&#xff0c;在使用上有多种方式(使用方式参考这篇文章&#xff1a;golang线程池ants-四种使用方法)&#xff0c;但是在实现的核心就一个&#xff0c;如下架构图&#xff1a; 总的来说&#xff0c;就是三个数据结构&#xff1a; Pool、WorkerStack、goW…

【前端实现】在父组件中调用公共子组件:注意事项逻辑示例 + 将后端数组数据格式转换为前端对象数组形式 + 增加和删除行

【前端】在父组件中调用公共子组件的实现方法 写在最前面一、调用公共子组件子组件CommonRow.vue父组件ParentComponent.vue 二、实现功能1. 将后端数组数据格式转换为前端对象数组形式2. 增加和删除row 三、小结 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2…

全景图三维3D模型VR全景上传展示H5开发

全景图三维3D模型VR全景上传展示H5开发 3D互动体验平台的核心功能概览 兼容广泛格式&#xff1a;支持OBJ、FBX、GLTF等主流及前沿3D模型格式的无缝上传与展示&#xff0c;确保创意无界。 动态交互探索&#xff1a;用户可自由旋转、缩放、平移模型&#xff0c;深度挖掘每一处…

STMF4 硬件IIC(天空星开发板)

前言&#xff1a;笔记参考立创开发文档&#xff0c;连接放在最后 #IIC概念介绍 #IIC介绍 IIC通信协议&#xff0c;一种常见的串行通信协议&#xff0c;英文全程是 Inter-Integrated Circuit 使用这种通信方式的模块&#xff0c;通常有SCL&#xff08;Serial Clock Line&…

pytest使用报错(以及解决pytest所谓的“抑制print输出”)

1. 测试类的类名问题 #codingutf-8import pytestclass TestClass1:def setup(self) -> None:print(setup)def test_01(self) -> None:print(test_01111111111111111111111)def test_02(self) -> None:print(test_02)以上述代码为例&#xff0c;如果类名是Test开头&am…

Chair Footrest Protective Cover

Chair Footrest Protective Cover 万能通用型椅子脚垫保护套凳子耐磨硅胶加厚垫桌椅脚垫防滑静音套

Docker逃逸CVE-2019-5736、procfs云安全漏洞复现,全文5k字,超详细解析!

Docker容器挂载procfs 逃逸 procfs是展示系统进程状态的虚拟文件系统&#xff0c;包含敏感信息。直接将其挂载到不受控的容器内&#xff0c;特别是容器默认拥有root权限且未启用用户隔离时&#xff0c;将极大地增加安全风险。因此&#xff0c;需谨慎处理&#xff0c;确保容器环…

我使用HarmonyOs Next开发了b站的首页

1.实现效果展示&#xff1a; 2.图标准备 我使用的是iconfont图标&#xff0c;下面为项目中所使用到的图标 3. 代码 &#xff08;1&#xff09;Index.ets&#xff1a; import {InfoTop} from ../component/InfoTop import {InfoCenter} from ../component/InfoCenter import…