SpringBoot 基于注解实现接口的代理Bean注入

在springboot加载时需自己手动将接口的代理bean注入到spring容器中,这样在service层注入该接口类型即可,

1.在SpringBoot启动类上添加EnableProxyBeanScan注解

  

EnableProxyBeanScan为自定义注解,通过Import注解扫描被ProxyBean注解的类或者被ProxyBean修饰的注解注解的类("注解继承")

ProxyBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar 通过ProxyInterfaceBeanBeanDefinitionScanner 来进行bean的加载

ProxyFactoryBean为bean的工厂类,提供代理bean

ProxyHandler为代理业务逻辑接口,提供三个参数: Class(被代理的类),Method(被代理的方法),Object[] 入参参数


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableProxyBeanScan.ProxyBeanDefinitionRegistrar.class)
public @interface EnableProxyBeanScan {

String[] basePackages() default {};

class ProxyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ProxyInterfaceBeanBeanDefinitionScanner scanner = new ProxyInterfaceBeanBeanDefinitionScanner(registry);
scanner.scan(getBasePackages(importingClassMetadata));
}

private String[] getBasePackages(AnnotationMetadata importingClassMetadata){
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableProxyBeanScan.class.getCanonicalName());
Set<String> basePackages = new HashSet();
String[] basePackagesArr = (String[])((String[])attributes.get("basePackages"));
for(String item: basePackagesArr){
if(StringUtils.hasText(item))
basePackages.add(item);
}

if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}

return basePackages.toArray(new String[basePackages.size()]);
}
}
}
 
public class ProxyInterfaceBeanBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public ProxyInterfaceBeanBeanDefinitionScanner(BeanDefinitionRegistry registry) {
//registry是Spring的Bean注册中心
// false表示不使用ClassPathBeanDefinitionScanner默认的TypeFilter
// 默认的TypeFilter只会扫描带有@Service,@Controller,@Repository,@Component注解的类
super(registry,false);
} @Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
addIncludeFilter(new AnnotationTypeFilter(ProxyBean.class));
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
if (beanDefinitionHolders.isEmpty()){
System.err.println("No Interface Found!");
}else{
//创建代理对象
createBeanDefinition(beanDefinitionHolders);
}
return beanDefinitionHolders;
} @Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isInterface() || metadata.isAbstract();
} /**
* 为扫描到的接口创建代理对象
* @param beanDefinitionHolders
*/
private void createBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
GenericBeanDefinition beanDefinition = ((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition());
//将bean的真实类型改变为FactoryBean
beanDefinition.getConstructorArgumentValues().
addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClass(ProxyFactoryBean.class);
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProxyBean { Class<? extends ProxyHandler> value();
}
public interface ProxyHandler{

    Object execute(Class<?> proxyType,Object proxy, Method proxyMethod, Object[] args);
}
public class ProxyFactoryBean<T> implements FactoryBean {

    private static final Map<Class<? extends ProxyHandler>,ProxyHandler> ProxyHandlers = new ConcurrentHashMap<>();

    private Class<T> interfaceClass;
private Class<? extends ProxyHandler> proxyHandlerType; public ProxyFactoryBean(Class<T> interfaceClass) {
this.interfaceClass = interfaceClass;
this.proxyHandlerType = AnnotationUtils.findAnnotation(interfaceClass, ProxyBean.class).value();
if(!ProxyFactoryBean.ProxyHandlers.containsKey(proxyHandlerType)) {
ProxyHandler proxyHandler = ClassUtils.newInstance(proxyHandlerType);
SpringBean.inject(proxyHandler);
ProxyFactoryBean.ProxyHandlers.put(proxyHandlerType, proxyHandler);
}
} @Override
public T getObject() throws Exception {
final ProxyHandler proxyHandler = ProxyFactoryBean.ProxyHandlers.get(proxyHandlerType);
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
(proxy,method,args) -> proxyHandler.execute(interfaceClass,proxy,method,args)
);
} @Override
public Class<T> getObjectType() {
return interfaceClass;
}
}

简单的例子:

  类似spring-feign的接口发送Http请求

1.先定义一个注解HttpClient,和HttpClientProxyHandler

@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ProxyBean(HttpClient.HttpClientProxyHandler.class)
public @interface HttpClient { @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Request{
String url();
RequestMethod method() default RequestMethod.POST;
}
/**简单定义下进行测试,实际实现肯定要比这个复杂*/
class HttpClientProxyHandler implements ProxyHandler {
/**这个类虽然没有被Spring管理,不过通过这个注解可以实现SpringBean的注入和使用,
* 见ProxyFactoryBean构造方法的代码
* SpringBean.inject(proxyHandler);
*/
@Autowired
private RestTemplate template;
@Override
public Object execute(Class<?> proxyType,Object proxy, Method proxyMethod, Object[] args) {
return template.postForObject(
proxyMethod.getAnnotation(Request.class).url()
,args[0]
,proxyMethod.getReturnType()
);
}
}
}

2.被代理的接口

@HttpClient
public interface LoginService { @HttpClient.Request(url="ddd")
String getUserAge(ExamineReqDto username);
}

3.测试,

测试这里没有细致的测,RestTemplate这里是成功拿到了,不影响后续的使用

最后,附Bean注入的代码:


@Component
public class SpringBean implements ApplicationContextAware {

private static final Logger log = LoggerFactory.getLogger(SpringBean.class);

private static ApplicationContext applicationContext;

private SpringBean(){}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBean.applicationContext = applicationContext;
}

public static <T> T getSpringBean(Class<T> clazz){
return SpringBean.applicationContext.getBean(clazz);
}
@SuppressWarnings("unchecked")
public static <T> T getSpringBean(String beanName){
return (T) SpringBean.applicationContext.getBean(beanName);
}

public static void inject(Object object){
if(object == null)
return;
Class clazz = object.getClass();
while (clazz != Object.class) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Autowired annotation = field.getAnnotation(Autowired.class);
if (annotation != null) {
Reflector.setFieldValue(object,field,SpringBean.getSpringBean(field.getType()));
}

Resource resource = field.getAnnotation(Resource.class);
if (resource != null) {
Reflector.setFieldValue(object,field,SpringBean.getSpringBean(field.getName()));
}
}
clazz = clazz.getSuperclass();
}
}

}
 

 补全Http请求代理接口

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ProxyCustomizer(HttpClientProxyHandler.class)
public @interface HttpClient { }

import com.sinosoft.demo.componment.proxy.core.ProxyHandler;
import javafx.util.Builder;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.*;

import static org.springframework.objenesis.instantiator.util.ClassUtils.newInstance;

public class HttpClientProxyHandler implements ProxyHandler {

@Override
public Object execute(Class<?> proxyType, Object proxy ,Method proxyMethod, Object[] args) {

if(proxyMethod.isDefault()){ //不对default 的方法进行代理
try {
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);

Class<?> declaringClass = proxyMethod.getDeclaringClass();
int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;

return constructor.newInstance(declaringClass, allModes)
.unreflectSpecial(proxyMethod, declaringClass)
.bindTo(proxy)
.invokeWithArguments(args);
}catch(Exception ex) {
throw new RuntimeException(ex);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}

RequestMapping requestMapping = AnnotationUtils.getAnnotation(proxyMethod, RequestMapping.class);
String url = getRequestUrl(requestMapping);
Object invokeParam = handleRequestObject(proxyMethod,args);
Class<?> returnType = getReturnType(proxyMethod);
MultiValueMap<String, String> httpAttributes = getHttpAttributes(proxyType, proxyMethod);
RestTemplate template = createRestTemplate(httpAttributes,proxyType,proxyMethod);
HttpEntity entity = new HttpEntity(invokeParam,headers(httpAttributes));
ResponseEntity<?> responseEntity = template.exchange(url,getHttpMethod(requestMapping),entity,returnType,args);
return handleReturnObject(proxyMethod,invokeParam,responseEntity);
}

private String getRequestUrl(RequestMapping requestMapping){
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(requestMapping);
String[] path = (String[]) annotationAttributes.get("path");
if(path.length>0)
return path[0];
throw new UnsupportedOperationException("url not be null!");
}

private Class<?> getReturnType(Method proxyMethod){
Class<?> returnType = proxyMethod.getReturnType();
if(ClassUtils.isAssignable(CallBack.class,returnType)){
Type[] interfaces = returnType.getGenericInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (interfaces[i] instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) interfaces[i];
if (parameterizedType.getRawType() == CallBack.class) {
return (Class<?>) parameterizedType.getActualTypeArguments()[0];
}
}
}
}
if(ClassUtils.isAssignable(ResponseEntity.class,returnType)){
Type genericReturnType = proxyMethod.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
return (Class<?>) actualTypeArguments[0];
}
}
return returnType;
}

private Object handleRequestObject(Method proxyMethod,Object[] args){
Object invokeParam;
if(args.length==1){
if(args[0] instanceof Builder)
invokeParam = ((Builder)args[0]).build();
else
invokeParam = args[0];
}else{
Map<String,Object> paramsMap = new LinkedHashMap<>();
Parameter[] parameters = proxyMethod.getParameters();
for(int i=0;i<parameters.length;i++){
RequestParam annotation = AnnotationUtils.findAnnotation(parameters[i], RequestParam.class);
if(annotation!=null)
paramsMap.put((String)AnnotationUtils.getDefaultValue(annotation),args[i]);
else
paramsMap.put(parameters[i].getName(),args[i]);
}
invokeParam = paramsMap;
}
return invokeParam;
}

private Object handleReturnObject(Method proxyMethod,Object invokeParam,ResponseEntity<?> responseEntity){
Object result = null;
if(ClassUtils.isAssignable(CallBack.class,proxyMethod.getReturnType())){
CallBack callBack = (CallBack) newInstance(proxyMethod.getReturnType());
callBack.call(responseEntity,invokeParam);
result = callBack;
}else if(ClassUtils.isAssignable(ResponseEntity.class,proxyMethod.getReturnType())){
result = responseEntity;
}else{
result = responseEntity.getBody();
}
return result;
}

private HttpMethod getHttpMethod(RequestMapping requestMapping){
RequestMethod[] requestMethod = requestMapping.method();
if(requestMethod.length>0)
return HttpMethod.valueOf(requestMethod[0].name());
return HttpMethod.POST;
}

private MultiValueMap<String,String> getHttpAttributes(Class<?> proxyType, Method proxyMethod){
MultiValueMap<String,String> attributes= new LinkedMultiValueMap<>();
HttpAttribute[] proxyTypeAttributeAnnotations = proxyType.getAnnotationsByType(HttpAttribute.class);
for(HttpAttribute attribute : proxyTypeAttributeAnnotations)
attributes.add(attribute.name(),attribute.value());
HttpAttribute[] proxyMethodAttributeAnnotations = proxyMethod.getAnnotationsByType(HttpAttribute.class);
for(HttpAttribute attribute : proxyMethodAttributeAnnotations)
attributes.add(attribute.name(),attribute.value());
return attributes;
}

private HttpHeaders headers(MultiValueMap<String,String> multiValueMap){
if(multiValueMap.containsKey(HttpAttribute.CONNECTION_TIMEOUT))
multiValueMap.remove(HttpAttribute.CONNECTION_TIMEOUT);
if(multiValueMap.containsKey(HttpAttribute.SOCKET_TIMEOUT))
multiValueMap.remove(HttpAttribute.SOCKET_TIMEOUT);
return new HttpHeaders(multiValueMap);
}

private RestTemplate createRestTemplate(MultiValueMap<String,String> attributes,Class<?> proxyType, Method proxyMethod){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if(attributes.containsKey(HttpAttribute.CONNECTION_TIMEOUT))
factory.setConnectTimeout(Integer.valueOf(attributes.getFirst(HttpAttribute.CONNECTION_TIMEOUT)));
if(attributes.containsKey(HttpAttribute.SOCKET_TIMEOUT))
factory.setReadTimeout(Integer.valueOf(attributes.getFirst(HttpAttribute.SOCKET_TIMEOUT)));
RestTemplate template = new RestTemplate();
template.setRequestFactory(new BufferingClientHttpRequestFactory(factory));

HashSet<Class<? extends ClientHttpRequestInterceptor>> interceptors = new LinkedHashSet<>();
HttpRequestInterceptors proxyTypeAnnotation = proxyType.getAnnotation(HttpRequestInterceptors.class);
HttpRequestInterceptors proxyMethodAnnotation = proxyMethod.getAnnotation(HttpRequestInterceptors.class);
if(proxyTypeAnnotation!=null) {
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyTypeAnnotation.include())
interceptors.add(interceptor);
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyTypeAnnotation.unInclude())
interceptors.remove(interceptor);
}
if(proxyMethodAnnotation!=null) {
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyMethodAnnotation.include())
interceptors.add(interceptor);
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyMethodAnnotation.unInclude())
interceptors.remove(interceptor);
}
List<ClientHttpRequestInterceptor> interceptorsList = template.getInterceptors();
for( Class<? extends ClientHttpRequestInterceptor> interceptor :interceptors)
interceptorsList.add(newInstance(interceptor));
return template;
}

}
 
public interface CallBack<T,R> {

    void call(ResponseEntity<T> entity, R requestParam);

}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(HttpAttribute.HttpAttributes.class)
public @interface HttpAttribute { String name();
String value(); String SOCKET_TIMEOUT = "http.socket.timeout";
String CONNECTION_TIMEOUT = "http.connection.timeout"; @Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface HttpAttributes {
HttpAttribute[] value();
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface HttpRequestInterceptors { Class<? extends ClientHttpRequestInterceptor>[] include() default {}; Class<? extends ClientHttpRequestInterceptor>[] unInclude() default {}; }

 CallBack(补充)---"集合"类结果数据

  CallBack类型返回值作为对响应报文实体的进一步封装,Http接口为我们有时可能希望返回集合类型的数据结果(List<Pojo>),该代理类没有直接提供针对集合类型返回值的封装,

比如保险系统针对被保人进行风控校验(第三方接口来校验,自己解析结果,"集合">0表示有风控风险,进行提示或者其他后续处理)

这里通过实现callBack来变相实现对象的迭代(foreach)操作,

public class InsuredRiskWarnInfo implements CallBack<ResponseDto,RequestDto>,Iterable<InsuredRiskWarnInfo>{

    private List<InsuredRiskWarnInfo> data;

    @Getter
private String name; //姓名 @Getter
private String code; //错误码 @Getter
private String message;//错误信息 public void merge(InsuredRiskWarnInfo info){
if(this.data == null)
this.data = info.data;
else
this.data.addAll(info.data);
} @Override
public void call(ResponseEntity<ResponseDto> entity, RequestDto requestParam) {
data = new ArrayList<>();
// if(entity.getStatusCode() != HttpStatus.OK)
// throw new RuntimeException(entity.getStatusCode().getReasonPhrase());
/**这里是具体的对响应对象的数据封装逻辑,假设一些数据*/
Map<String,String> map = new HashMap<>();
map.put("1","错误1");
map.put("2","错误2");
map.put("3","错误3");
for(Map.Entry<String,String> item : map.entrySet()){
InsuredRiskWarnInfo info = new InsuredRiskWarnInfo();
info.name = "某某人"; //可以从请求对象requestParam中取
info.code = item.getKey();
info.message = item.getValue();
info.data = this.data;
this.data.add(info);
}
} @Override
public Iterator<InsuredRiskWarnInfo> iterator() {
return data.iterator();
}

调用示例 :

        /**模拟请求后*/
InsuredRiskWarnInfo insuredRiskWarnInfo = new InsuredRiskWarnInfo();
insuredRiskWarnInfo.call(null,null); String format = "%s风控失败%s:%s";
for (InsuredRiskWarnInfo info : insuredRiskWarnInfo){
System.err.println(String.format(format,info.getName(),info.getCode(),info.getMessage()));
} /**
某某人风控失败1:错误1
某某人风控失败2:错误2
某某人风控失败3:错误3
*/

 

SpringBoot 基于注解实现接口的代理Bean注入的更多相关文章

  1. 基于注解的接口限流+统一session认证

    代码心得: 一个基本的做法:对于用户身份认证做到拦截器里,针对HandlerMethod进行统一拦截认证,根据方法上的注解标识,判别是否需要身份验证,并将查找出来的User实体存入ThreadLoca ...

  2. SpringBoot | 问题 | 注解方式下无法发现Bean

    在排除注解的问题后,考虑扫描类的位置, [SpringBoot项目的Bean装配默认规则是根据Application类所在的包位置从上往下扫描! “Application类”是指SpringBoot项 ...

  3. springboot基于注解动态配置多数据源以及多数据源的事务统一

    参考文档:https://www.cnblogs.com/zhangboyu/p/7622412.html https://blog.csdn.net/qq_34322777/article/deta ...

  4. springboot 基于@Scheduled注解 实现定时任务

    前言 使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式: 一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer) 前者相信大家都很熟悉, ...

  5. Java开发学习(十三)----基于注解开发定义第三方bean及注解开发总结

    在前面的博客中定义bean的时候都是在自己开发的类上面写个注解就完成了,但如果是第三方的类,这些类都是在jar包中,我们没有办法在类上面添加注解,这个时候该怎么办? 遇到上述问题,我们就需要有一种更加 ...

  6. Spring学习笔记 - 第二章 - 注解开发、配置管理第三方Bean、注解管理第三方Bean、Spring 整合 MyBatis 和 Junit 案例

    Spring 学习笔记全系列传送门: Spring学习笔记 - 第一章 - IoC(控制反转).IoC容器.Bean的实例化与生命周期.DI(依赖注入) [本章]Spring学习笔记 - 第二章 - ...

  7. Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.

    可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...

  8. 8 -- 深入使用Spring -- 4...5 AOP代理:基于注解的“零配置”方式

    8.4.5 基于注解的“零配置”方式 AspectJ允许使用注解定义切面.切入点和增强处理,而Spring框架则可识别并根据这些注解来生成AOP代理.Spring只是使用了和AspectJ 5 一样的 ...

  9. Springboot + redis + 注解 + 拦截器来实现接口幂等性校验

    Springboot + redis + 注解 + 拦截器来实现接口幂等性校验   1. SpringBoot 整合篇 2. 手写一套迷你版HTTP服务器 3. 记住:永远不要在MySQL中使用UTF ...

  10. 基于注解的springboot+mybatis的多数据源组件的实现

    通常业务开发中,我们会使用到多个数据源,比如,部分数据存在mysql实例中,部分数据是在oracle数据库中,那这时候,项目基于springboot和mybatis,其实只需要配置两个数据源即可,只需 ...

随机推荐

  1. MySQL中为什么要使用索引合并(Index Merge)?

    本文分享自华为云社区<[华为云MySQL技术专栏]MySQL中为什么要使用索引合并(Index Merge)?>,作者:GaussDB 数据库. 在生产环境中,MySQL语句的where查 ...

  2. 聊天chat封装

    说明:连接状态,客户端ID,在线状态,连接中,当前聊天会话ID,当前聊天对象ID,总未读数, 聊天功能实现首先要保证当前用户已经登录状态 监听登录时更新会话列表 监听退出时更新会话列表 发起聊天的时候 ...

  3. 解决方案 | pywintypes.com_error: (-2147418111, '被呼叫方拒绝接收呼叫。', None, None)

    解决方案:加一个time.sleep(1)即可

  4. vue 理解yarn start 和yarn dev的区别

    yarn dev,当文件变动后,会自动重启. yanr start不会自动重启 nodemon会监听文件变动,跟yarn dev和yarn start无关.

  5. Profibus_DP转ModbusTCP网关模块接马保通讯案例

    某工业企业为了提高生产效率和管理水平,决定对其生产线进行智能化改造.在该项目中,利用巴图自动化Profibus_DP转ModbusTCP网关模块(BT-ETHPB20)连接了不同生产设备,实现了设备之 ...

  6. TIER 0: Dancing

    TIER 0: Dancing SMB Server Message Block 是一种网络协议,用于在计算机网络上共享文件.打印机和其他资源.它最初由微软开发,用于在 Windows 操作系统之间进 ...

  7. 文件系统(十):一文看懂 UBI 文件系统

    liwen01 2024.07.21 前言 UBI (Unsorted Block Images)文件系统是一种用于裸 flash 的文件系统管理层.它是专为管理原始闪存设备而设计,特别适用于嵌入式系 ...

  8. linux性能资源分析工具

    linux性能资源分析工具 1,top 2,ps 3,uptime 4,mpstat 5,pidstat 6,vmstat 7,iostat 8,netstat 9,lsof 10,sar / nmo ...

  9. Jmeter参数化2-读取文件

    如果你想要jmeter批量生成 指定 的参数值数据,可以使用导入文件参数化方法来实现 下面讲述两种jmeter读取文件参数值方法:"CSV 数据文件设置"."函数助手&q ...

  10. 对比python学julia(第三章:游戏编程)--(第二节)公主迎圣诞(4)

    4.  碰撞检测 .得分及生命 在第 4 个阶段,利用GameZero的碰撞检测功能,使公主角色能够接到雪花 .礼物或剪刀. 在"sdgz"项目目录中 ,把 version3.jl ...