Spring Boot自动装配原理以及实践

了解自动装配两个核心

@Import注解的作用

@Import说Spring框架经常会看到的注解,它有以下几个作用:

  1. 导入@Configuration类下所有的@bean方法中创建的bean
  2. 导入import指定的bean,例如@Import(AService.class),就会生成AServicebean,并将其导入到Spring容器中。
  3. 结合ImportSelector接口类导如指定类。(后文会展开介绍)

ImportSelector详解

ImportSelector接口则是前者的辅助者,如果我们希望可以选择性的导入一些类,我们就可以继承ImportSelector接口编写一个ImportSelector类,告知容器需要导入的类。就以Spring Boot为例,它有个@EnableAutoConfiguration注解,其工作原理就是基于内部的@Import({AutoConfigurationImportSelector.class})注解将AutoConfigurationImportSelector导入容器中,Spring就会调用其selectImports方法获取需要导入的类,并将这些类导入容器中。

@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);//返回需要导入的类的字符串数组return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}

使用示例

可能上文的原理对没有接触源码的读者比较模糊,所以我们不妨写一个demo来了解一下这个注解。我们现在有一个需求,希望通过import注解按需将Student类或者User类导入容器中。首先我们看看user类代码,没有任何实现,代码示例如下:

public class User {
}

Student 类代码同理,没有任何实现仅仅做测试使用

public class Student {
}

完成测试类的创建之后,我们就以用户类为例,创建UserConfig 代码如下:

@Configuration
public class UserConfig {@Beanpublic User getUser() {return new User();}
}

然后编写ImportSelector 首先类,编写自己的导入逻辑,可以看到笔者简单实现了一个selectImports方法返回UserConfig的类路径。

public class CustomImportSelector implements ImportSelector {private static Logger logger = LoggerFactory.getLogger(CustomImportSelector.class);/*** importingClassMetadata:被修饰的类注解信息*/@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {logger.info("获取到的注解类型:{}",importingClassMetadata.getAnnotationTypes().toArray());// 如果被CustomImportSelector导入的组件是类,那么我们就实例化UserConfigif (!importingClassMetadata.isInterface()) {return new String[] { "com.example.UserConfig" };}// 此处不要返回nullreturn new String[] { "com.example.StudentConfig" };}
}

完成这些步骤我们就要来到最关键的一步了,在Spring Boot启动类中使用@Import导入CustomImportSelector

@SpringBootApplication
@Configuration
@Import(CustomImportSelector.class)
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}

为了测试我们编写这样一个controller看看bean是否会导入到容器中

@RestController
public class MyController {private static Logger logger = LoggerFactory.getLogger(MyController.class);@Autowiredprivate User user;@RequestMapping("hello")public String hello() {logger.info("user:{}", user);return "hello";}
}

结果测试我们发现user不为空,说明CustomImportSelector确实将UserConfig导入到容器中,并将User导入到容器中了。

从源码角度了解ImportSelector工作原理

关于源码分析其实也很好做,感兴趣的读者可以直接在CustomImportSelector打个断点就能知道工作原理了:

在这里插入图片描述

断点之后我们不妨用以终为始的方式了解一下过程,首先入口是AbstractApplicationContextrefresh()方法,它会调用一个invokeBeanFactoryPostProcessors(beanFactory);进行bean工厂后置操作

@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {.........invokeBeanFactoryPostProcessors(beanFactory);........}
}

步入代码,可以看到容器会不断遍历各个postProcessor 即容器后置处理器,然后执行他们的逻辑

for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {.....//执行各个postProcessor 的逻辑invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
}

重点来了,遍历过程中得到一个ConfigurationClassPostProcessor,这个类就会得到我们的CustomImportSelector,然后执行selectImports获取需要导入的类信息,最终会生成一个Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());

如下图所示可以看到configClasses就包含UserConfig

在这里插入图片描述

总结一下核心流程的时序图

在这里插入图片描述

完成上述步骤后ConfigurationClassPostProcessor就会通过这个set集合执行loadBeanDefinitions方法将需要的bean导入到容器中,进行后续IOC操作。

在这里插入图片描述

上图代码如下所示:

//configClasses 中就包含了UserConfig类
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);//执行	loadBeanDefinitions	this.reader.loadBeanDefinitions(configClasses);

Spring Boot自动装配原理(重点)

了解了import原理后,我们了解Spring Boot自动装配原理也很简单了,我们不妨看看Spring Boot@SpringBootApplication这个注解中包含一个@EnableAutoConfiguration注解,我们不妨点入看看,可以看到它包含一个@Import(AutoConfigurationImportSelector.class)注解,从名字上我们可以知晓这是一个ImportSelector的实现类。

所以我们不妨看看它的selectImports逻辑,可以看到它会通过getAutoConfigurationEntry方法获取需要装配的类,然后通过StringUtils.toStringArray切割返回。所以我们不妨看看getAutoConfigurationEntry

@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}

查看getAutoConfigurationEntry方法,我们可以看到它通过getCandidateConfigurations获取各个xxxxAutoConfigure,并返回结果

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);//获取所有xxxxAutoConfigureList<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//移除不需要的configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);//返回结果return new AutoConfigurationEntry(configurations, exclusions);}

在这里插入图片描述

getCandidateConfigurations实际上是会通过一个loadSpringFactories方法,如下所示遍历获取所有含有META-INF/spring.factoriesjar

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = (Map)cache.get(classLoader);if (result != null) {return result;} else {HashMap result = new HashMap();try {//解析这个配置文件获取所有配置类然后返回Enumeration urls = classLoader.getResources("META-INF/spring.factories");.....return result;} catch (IOException var14) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);}}}

最终结果过滤解析,回到我们上文说的beanDefinitionMap中,最终通过IOC完成自动装配。

实践1-手写Spring Boot Starter中间件

了解自动装配我们不妨自己写一个中间件实践一下,现在需求如下,我们希望某些类的接口只有某几个用户可以访问,所以我们希望编写一个中间件判断请求用户是什么身份,如果没有权限则直接返回报错。

首先我们编写一个注解DoDoor ,用key记录传入的用户idreturnJson返回没有权限的响应结果

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {String key() default "";String returnJson() default "";}

然后在编写StarterServiceProperties ,使用ConfigurationPropertiesitstack.door前缀的值和当前类userStr绑定。

/*** 通过"itstack.door前缀的配置获取userStr信息*/
@ConfigurationProperties("itstack.door")
public class StarterServiceProperties {private String userStr;public String getUserStr() {return userStr;}public void setUserStr(String userStr) {this.userStr = userStr;}}

完成后在编写StarterService 这个类会将userStr切割成数组,例如我们传111,222,最终就会得到[111,222]

public class StarterService {private String userStr;public StarterService(String userStr) {this.userStr = userStr;}public String[] split(String separatorChar) {return StringUtils.split(this.userStr, separatorChar);}}

这些佐料写完之后,我们就可以编写一个AOP类了,可以看到这个AOP做的是很简单,就是拦截带有DoDoor的请求,将注解key配置的值和我们的userStr数组比对,若包含则放行,反之拦截。

@Aspect
@Component
public class DoJoinPoint {private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);@Autowiredprivate StarterService starterService;@Pointcut("@annotation(org.itstack.door.annotation.DoDoor)")public void aopPoint() {}@Around("aopPoint()")public Object doRouter(ProceedingJoinPoint jp) throws Throwable {//获取内容Method method = getMethod(jp);DoDoor door = method.getAnnotation(DoDoor.class);//获取字段值String keyValue = getFiledValue(door.key(), jp.getArgs());logger.info("itstack door handler method:{} value:{}", method.getName(), keyValue);if (null == keyValue || "".equals(keyValue)) return jp.proceed();//配置内容String[] split = starterService.split(",");//白名单过滤for (String str : split) {if (keyValue.equals(str)) {return jp.proceed();}}//拦截return returnObject(door, method);}private Method getMethod(JoinPoint jp) throws NoSuchMethodException {Signature sig = jp.getSignature();MethodSignature methodSignature = (MethodSignature) sig;return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());}private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {return jp.getTarget().getClass();}//返回对象private Object returnObject(DoDoor doGate, Method method) throws IllegalAccessException, InstantiationException {Class<?> returnType = method.getReturnType();String returnJson = doGate.returnJson();if ("".equals(returnJson)) {return returnType.newInstance();}return JSON.parseObject(returnJson, returnType);}//获取属性值private String getFiledValue(String filed, Object[] args) {String filedValue = null;for (Object arg : args) {try {if (null == filedValue || "".equals(filedValue)) {filedValue = BeanUtils.getProperty(arg, filed);} else {break;}} catch (Exception e) {if (args.length == 1) {return args[0].toString();}}}return filedValue;}}

编写我们的AutoConfigure ,根据条件决定上述的类是否导入

@Configuration
@ConditionalOnClass(StarterService.class)
@EnableConfigurationProperties(StarterServiceProperties.class)
public class StarterAutoConfigure {@Autowiredprivate StarterServiceProperties properties;@Bean@ConditionalOnMissingBean@ConditionalOnProperty(prefix = "itstack.door", value = "enabled", havingValue = "true")StarterService starterService() {return new StarterService(properties.getUserStr());}@Bean@ConditionalOnMissingBean@ConditionalOnProperty(prefix = "itstack.door", value = "enabled", havingValue = "true")DoJoinPoint doJoinPoint() {return new DoJoinPoint();}}

完成后编写一个spring.factories,导入这个AutoConfigure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.itstack.door.config.StarterAutoConfigure

修改一下pom,本地打个包

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.3.2</version><configuration><archive><addMavenDescriptor>false</addMavenDescriptor><index>true</index><manifest><addDefaultSpecificationEntries>true</addDefaultSpecificationEntries><addDefaultImplementationEntries>true</addDefaultImplementationEntries></manifest><manifestEntries><Implementation-Build>${maven.build.timestamp}</Implementation-Build></manifestEntries></archive></configuration>
</plugin>			

在其他应用中导入

<dependency><groupId>org.itatack.demo</groupId><artifactId>door-spring-boot-starter</artifactId><version>1.0.1-SNAPSHOT</version></dependency>

编写配置

server:port: 9887spring:application:name: demo# 自定义中间件配置
itstack:door:enabled: trueuserStr: 1001,aaaa,ccc #白名单用户ID,多个逗号隔开

然后在导入这个中间件的应用中编写一个方法测试@DoDoor

@RestController
public class HelloWorldController {@Autowiredprivate ApplicationContext applicationContext;@DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名单可访问用户拦截!\"}")@RequestMapping(path = "/user", method = RequestMethod.GET)public Map queryUserInfo(@RequestParam String userId) {Map<String, DoJoinPoint> beansOfType = applicationContext.getBeansOfType(DoJoinPoint.class);Map resultMap = new HashMap<>();resultMap.put("虫虫:" + userId, "天津市南开区旮旯胡同100号");return resultMap;}}

测试结果

C:\Users\xxxx>curl http://localhost:9887/user?userId=1001132
{"code":"1111","info":"非白名单可访问用户拦截!"}
C:\Users\xxx>curl http://localhost:9887/user?userId=1111
{"code":"1111","info":"非白名单可访问用户拦截!"}
C:\Users\xx>curl http://localhost:9887/user?userId=1001
{"虫虫:1001":"天津市南开区旮旯胡同100号"}

源码是借鉴小傅哥的,感兴趣的读者可以参考:

Spring Boot 中间件开发(一)《服务治理中间件之统一白名单验证》

实践2-通用日志组件

需求介绍

微服务项目中,基于日志排查问题是非常重要的手段,而日志属于非功能范畴的一个职责,所以我们希望将日志打印和功能解耦。AOP就是非常不错的手段,但是在每个服务中都编写一个切面显然是非常不可取的。
所以我们希望通过某种手段会编写一个通用日志打印工具,只需一个注解即可实现对方法的请求响应进行日志打印。
所以我们这个例子仍然是利用自动装配原理编写一个通用日志组件。

实现步骤

  1. 创建日志插件模块cloud-component-logging-starter,并引入我们需要的依赖,如下所示,因为笔者要对spring-web应用进行拦截所以用到的starter-webaop模块,以及为了打印响应结果,笔者也用到hutool,完整的依赖配置如下所示:
 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency></dependencies>
  1. 编写日志注解,如下所示,该注解的value用于记录当前方法要执行的操作,例如某方法上@SysLog("获取用户信息"),当我们的aop拦截到之后,就基于该注解的value打印该方法的功能。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLog {/*** 记录方法要执行的操作** @return*/String value();
}
  1. 编写环绕切面逻辑,代码如下所示,逻辑非常简单,拦截到了切面后若报错则打印报错的逻辑,反之打印正常请求响应结果。
@Aspect
public class SysLogAspect {private static Logger logger = LoggerFactory.getLogger(SysLogAspect.class);@Pointcut("@annotation(com.zsy.annotation.SysLog)")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();//类名String className = joinPoint.getTarget().getClass().getName();//方法名String methodName = signature.getName();SysLog syslog = method.getAnnotation(SysLog.class);//获取当前方法进行的操作String operator =syslog.value();long beginTime = System.currentTimeMillis();Object returnValue = null;Exception ex = null;try {returnValue = joinPoint.proceed();return returnValue;} catch (Exception e) {ex = e;throw e;} finally {long cost = System.currentTimeMillis() - beginTime;if (ex != null) {logger.error("业务请求:[类名: {}][执行方法: {}][执行操作: {}][耗时: {}ms][请求参数: {}][发生异常]",className, methodName, operator, joinPoint.getArgs(), ex);} else {logger.info("业务请求:[类名: {}][执行方法: {}][执行操作: {}][耗时: {}ms][请求参数: {}][响应结果: {}]",className, methodName, operator, cost, joinPoint.getArgs(), JSONUtil.toJsonStr(returnValue));}}}
}
  1. 编写配置类
@Configuration
public class SysLogAutoConfigure {@Beanpublic SysLogAspect getSysLogAspect() {return new SysLogAspect();}
}
  1. 新建spring.factories告知要导入Spring容器的类,内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zsy.config.SysLogAutoConfigure
  1. 其他服务引入进行测试,以笔者为例,方法如下
@SysLog("获取用户信息")@GetMapping("getByCode/{accountCode}")public ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode) {log.info("远程调用feign接口,请求参数:{}", accountCode);return accountFeign.getByCode(accountCode);}

请求之后输出结果如下

2023-02-16 00:08:08,085 INFO  SysLogAspect:58 - 业务请求:[类名: com.zsy.order.controller.OrderController][执行方法: getByCode][执行操作: 获取用户信息][耗时: 892ms][请求参数: [zsy]][响应结果: {"data":{"accountCode":"zsy","amount":10000,"accountName":"zsy","id":1},"message":"操作成功","success":true,"status":100,"timestamp":1676477287856}]

参考文献

SpringBoot 自动装配原理

@Import、ImportSelector注解使用及源码分析

SpringBoot封装我们自己的Starter

Spring Boot 中间件开发(一)《服务治理中间件之统一白名单验证》

SpringCloud Alibaba微服务实战三十一 - 业务日志组件

Spring全解系列 - @Import注解

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

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

相关文章

scala学习五:字符串

一、字符串 Scala中的字符串类型实际上是Java String&#xff0c;它本身没有String Scala中&#xff0c;string是不可变对象&#xff0c;不可修改。如果修改字符串&#xff0c;会产生新的字符串对象 1.1 Scala字符串 创建字符串实例 var greeting Hello World var greetin…

综合物业服务标书:打造舒适宜居的生活环境

综合物业服务标书&#xff0c;是指为了竞标或招投标而编写的综合物业服务方案书。综合物业服务标书是物业管理公司向业主委托方展示自身实力和服务水平的重要文件&#xff0c;也是获得物业管理合同的关键。 一、综合物业服务的定义 综合物业服务是指物业管理公司为业主提供的…

杰发科技AC7840——SPM电源管理之低功耗模式

0、SPM简介 很早以前就听过低功耗模式&#xff0c;一直没有怎么深入了解&#xff0c;最近遇到几个项目都是跟低功耗有关。正好AutoChips的芯片都有电源管理的功能&#xff0c;在此借用AC7840的SPM对低功耗进行测试。 1、AC7840的5种功耗模式 2、AC7840的模式转换 3、唤醒 在…

【华为数据之道学习笔记】5-11 算法模型设计

算法是指训练、学习模型的具体计算方法&#xff0c;也就是如何求解全局最优解&#xff0c;并使得这个过程高效且准确&#xff0c;其本质上是求数学问题的最优化解&#xff0c;即算法是利用样本数据生成模型的方法。算法模型是根据业务需求&#xff0c;运用数学方法对数据进行建…

小程序使用web-view无法打开该H5页面不支持打开的解决方法

我在正式上线版小程序使用 web-view 组件测试时提示&#xff1a;“无法打开该页面&#xff0c;不支持打开 https://xxxxxx&#xff0c;请在“小程序右上角更多->反馈与投诉”中和开发者反馈。” 奇怪的是&#xff0c;“真机调试”、“开发模式”都可以使用 web-view 组件访…

面试题总结(五)【线程的同步和互斥】【华清远见西安中心】

线程的同步实现方式有几种&#xff0c;区别是什么&#xff1f; 线程的同步可以使用多种方式来实现&#xff0c;常见的包括互斥量、条件变量、原子操作、信号量等。它们的区别主要体现在实现机制、适用场景和性能特点上。 1. 互斥量&#xff08;Mutex&#xff09;&#xff1a;互…

易优建站修改文章模板中的地图大小

我是新建了一个栏目放置的百度地图 然后再这个栏目下创建地图 然后再页面中添加代码标签&#xff0c;显示地图&#xff0c;其实就是显示文章的内容 接下来就是修改地图的大小 第一步&#xff1a;打开文件public/plugins/Ueditor/dialogs/map/show.html 第二步&#xff1a;打开…

从西工大安全事件浅谈特权账号管理系统

去年9月&#xff0c;国家计算机病毒应急处理中心发布《西北工业大学遭美国NSA网络攻击事件调查报告&#xff08;之一&#xff09;》&#xff08;以下简称“西工大事件报告”&#xff09;&#xff0c;以充分详实的证据揭示了美国NSA使用41种武器&#xff0c;先后使用了遍布17个国…

Wireshark在网络性能调优中的应用

第一章&#xff1a;Wireshark基础及捕获技巧 1.1 Wireshark基础知识回顾 1.2 高级捕获技巧&#xff1a;过滤器和捕获选项 1.3 Wireshark与其他抓包工具的比较 第二章&#xff1a;网络协议分析 2.1 网络协议分析&#xff1a;TCP、UDP、ICMP等 2.2 高级协议分析&#xff1a;HTTP…

长短期记忆(LSTM)神经网络-多输入回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分程序&#xff1a; 四、完整代码数据下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编…

数据可视化Seaborn

数据可视化Seaborn Seaborn简介Seaborn API第一个Seaborn应用Seaborn基本概念Seaborn图表类型Seaborn数据集Seaborn样式Seaborn调色板Seaborn分面网格Seaborn统计图表Seaborn散点图Seaborn折线图Seaborn柱状图Seaborn箱线图Seaborn核密度估计图Seaborn分类散点图Seaborn回归分…

AI降重工具

WEB版 体验一下 from docx import Document import requestsdef call_api_and_get_content(content, prompt):api_url "http://XXXXXXXX/api?content" content promptresponse requests.get(api_url)if response.status_code 200:api_result response.text.re…

el-select如何去掉placeholder属性

功能要求是&#xff1a;当el-select的disabled属性为true的时候不展示“请选择”字样 1、要去掉 el-select 元素的 placeholder 属性&#xff0c;可以在代码中将其设置为空字符串。 <el-select placeholder"" ...></el-select> 注意&#xff1a;这种方…

1_js基本简介数据类型变量的使用

1. 编程语言简介 1.1 计算机编程语言 计算机编程语言是程序设计的最重要的工具&#xff0c;它是指计算机能够接受和处理的、具有一定语法规则的语言。从计算机诞生&#xff0c;计算机语言经历了机器语言、汇编语言和高级语言几个阶段。 高级语言&#xff1a;JavaScript&#x…

el-select二次封装实现可分页加载数据

使用el-select时一次性渲染几百条数据时会造成页面克顿, 可以通过分页来实现, 这里我用的方式为默认获取全部数据, 然后一次性截取10条进行展示, 滚动条触底后会累加, 大家也可以优化为滚动条触底后发送请求去加载数据 创建自定义指令customizeFocus用户懒加载 在utils文件夹(…

游戏出海-选择云观测监控

相较于国内发展企业&#xff0c;出海企业能更加快速自身发展&#xff0c;但也相对的获取到的业务压力也远远高于国内。对于游戏出海企业&#xff0c;各地玩家带来的庞大数据&#xff0c;企业需要时时刻刻监控玩家数据波动、服务器运行、有无外挂篡改等等。九河云通过自身多年从…

使用Httpclient来替代客户端的jsonp跨域解决方案

最近接手一个项目&#xff0c;新项目需要调用老项目的接口&#xff0c;但是老项目和新项目不再同一个域名下&#xff0c;所以必须进行跨域调用了&#xff0c;但是老项目又不能进行任何修改&#xff0c;所以jsonp也无法解决了&#xff0c;于是想到了使用了Httpclient来进行服务端…

ZETA及纵行科技入选《2024年中国AIoT产业全景图谱》非蜂窝无线通信板块

12月15日&#xff0c;中国AIoT产业年会暨2023年智能产业前瞻洞察大典在深圳隆重举行&#xff0c;智次方研究院在本次大会上正式发布了全新升级版的《2024年中国AIoT产业全景图谱报告》。ZETA及ZETA联盟核心会员纵行科技、中国铁塔、中移物联、广芯微电子入选非蜂窝无线通信板块…

[MySQL] MySQL中的用户管理

文章目录 一、用户 1、1 用户信息 1、2 创建用户 1、3 删除用户 1、4 修改用户密码 二、数据库的权限 2、1 用户权限 2、2 回收权限 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x1f64b;‍♂️ &#x1f440; 专栏&#xff1a;MySQL &#x1f440; &#x1f4a5; 标题…

Mysql的多表连接

文章目录 表连接1. 表连接的分类2.内连接简介3.外连接简介(1)左连接和右连接 4.子查询简介(1)子查询是一种查询中嵌套查询的语句(2)子查询的分类 表连接 1. 表连接的分类 &#xff08;1&#xff09;表连接分为两种&#xff1a;内连接和外连接。 &#xff08;2&#xff09;内连…