static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类
生命太短暂,不要去做一些根本没有人想要的东西。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。
前言
各位小伙伴大家好,我是A哥。上篇文章了解了static关键字 + @Bean方法的使用,知晓了它能够提升Bean的优先级,在@Bean方法前标注static关键字,特定情况下可以避免一些烦人的“警告”日志的输出,排除隐患让工程变得更加安全。我们知道static关键字它不仅可使用在方法上,那么本文将继续挖掘static在Spring环境下的用处。
根据所学的JavaSE基础,static关键字除了能够修饰方法外,还能使用在这两个地方:
- 修饰类。确切的说,应该叫修饰内部类,所以它叫静态内部类
- 修饰成员变量
其实static还可以修饰代码块、static静态导包等,但很明显,这些与本文无关
接下来就以这为两条主线,分别研究static在对应场景下的作用,本文将聚焦在静态内部类上。
版本约定
本文内容若没做特殊说明,均基于以下版本:
- JDK:
1.8
- Spring Framework:
5.2.2.RELEASE
正文
说到Java里的static关键字,这当属最基础的入门知识,是Java中常用的关键字之一。你平时用它来修饰变量和方法了,但是对它的了解,即使放在JavaSE情景下知道这些还是不够的,问题虽小但这往往反映了你对Java基础的了解程度。
当然喽,本文并不讨论它在JavaSE下使用,毕竟咱们还是有一定逼格的专栏,需要进阶一把,玩玩它在Spring环境下到底能够迸出怎么样的火花呢?比如静态内部类~
Spring下的静态内部类
static修饰类只有一种情况:那就是这个类属于内部类,这就是我们津津乐道的静态内部类,形如这样:
public class Outer {
private String name;
private static Integer age;
// 静态内部类
private static class Inner {
private String innerName;
private static Integer innerAge;
public void fun1() {
// 无法访问外部类的成员变量
//System.out.println(name);
System.out.println(age);
System.out.println(innerName);
System.out.println(innerAge);
}
}
public static void main(String[] args) {
// 静态内部类的实例化并不需要依赖于外部类的实例
Inner inner = new Inner();
}
}
在实际开发中,静态内部类的使用场景是非常之多的。
认识静态/普通内部类
由于一些小伙伴对普通内部类 vs 静态内部类傻傻分不清,为了方便后续讲解,本处把关键要素做简要对比说明:
- 静态内部类可以声明静态or实例成员(属性和方法);而普通内部类则不可以声明静态成员(属性和方法)
- 静态内部类实例的创建不依赖于外部类;而普通外部类实例创建必须先有外部类实例才行(绑定关系拿捏得死死的,不信你问郑凯)
- 静态内部类不能访问外部类的实例成员;而普通内部类可以随意访问(不管静态or非静态) --> 我理解这是普通内部类能 “存活” 下来的最大理由了吧
总之,普通内部类和外部类的关系属于强绑定,而静态内部类几乎不会受到外部类的限制,可以游离单独使用。既然如此,那为何还需要static静态内部类呢,直接单独写个Class类岂不就好了吗?存在即合理,这么使用的原因我个人觉得有如下两方面思考,供以你参考:
- 静态内部类是弱关系并不是没关系,比如它还是可以访问外部类的static的变量的不是(即便它是private的)
- 高内聚的体现
在传统Spirng Framework
的配置类场景下,你可能鲜有接触到static关键字使用在类上的场景,但这在Spring Boot下使用非常频繁,比如属性配置类的典型应用:
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
// server.port = xxx
// server.address = xxx
private Integer port;
private InetAddress address;
...
// tomcat配置
public static class Tomcat {
// server.tomcat.protocol-header = xxx
private String protocolHeader;
...
// tomcat内的log配置
public static class Accesslog {
// server.tomcat.accesslog.enabled = xxx
private boolean enabled = false;
...
}
}
}
这种嵌套case使得代码(配置)的key 内聚性非常强,使用起来更加方便。试想一下,如果你不使用静态内部类去集中管理这些配置,每个配置都单独书写的话,像这样:
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
}
@ConfigurationProperties(prefix = "server.tomcat", ignoreUnknownFields = true)
public class TomcatProperties {
}
@ConfigurationProperties(prefix = "server.tomcat.accesslog", ignoreUnknownFields = true)
public class AccesslogProperties {
}
这代码,就问你,如果是你同事写的,你骂不骂吧!用臃肿来形容还是个中意词,层次结构体现得也非常的不直观嘛。因此,对于这种属性类里使用静态内部类是非常适合,内聚性一下子高很多~
除了在内聚性上的作用,在Spring Boot中的@Configuration
配置类下(特别常见于自动配置类)也能经常看到它的身影:
@Configuration(proxyBeanMethods = false)
public class WebMvcAutoConfiguration {
// web MVC个性化定制配置
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
...
}
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
...
}
}
利用静态内部类把相似配置类归并在一个 .java文件 内,这样多个static类还可公用外部类的属性、方法,也是一种高内聚的体现。同时static关键字提升了初始化的优先级,比如本例中的EnableWebMvcConfiguration
它会优先于外部类加载~
关于static静态内部类优先级相关是重点,静态内部类的优先级会更高吗?使用普通内部能达到同样效果吗?拍脑袋直接回答是没用的,带着这两个问题,接下来A哥举例领你一探究竟...
static静态配置类提升配置优先级
自己先构造一个Demo,场景如下:
@Configuration
class OuterConfig {
OuterConfig() {
System.out.println("OuterConfig init...");
}
@Bean
static Parent parent() {
return new Parent();
}
@Configuration
private static class InnerConfig {
InnerConfig() {
System.out.println("InnerConfig init...");
}
@Bean
Daughter daughter() {
return new Daughter();
}
}
}
测试程序:
@ComponentScan
public class TestSpring {
public static void main(String[] args) {
new AnnotationConfigApplicationContext(TestSpring.class);
}
}
启动程序,结果输出:
InnerConfig init...
OuterConfig init...
Daughter init...
Parent init...
结果细节:似乎都是按照字母表的顺序来执行的。I在前O在后;D在前P在后;
看到这个结果,如果你就过早的得出结论:静态内部类优先级高于外部类,那么就太随意了,图样图森破啊。大胆猜想,小心求证 应该是程序员应有的态度,那么继续往下看,在此基础上我新增加一个静态内部类:
@Configuration
class OuterConfig {
OuterConfig() {
System.out.println("OuterConfig init...");
}
@Bean
static Parent parent() {
return new Parent();
}
@Configuration
private static class PInnerConfig {
PInnerConfig() {
System.out.println("PInnerConfig init...");
}
@Bean
Son son() {
return new Son();
}
}
@Configuration
private static class InnerConfig {
InnerConfig() {
System.out.println("InnerConfig init...");
}
@Bean
Daughter daughter() {
return new Daughter();
}
}
}
我先解释下我这么做的意图:
- 增加一个字母P开头的内部类,自然顺序P在O(外部类)后面,消除影响
- P开头的内部类在源码摆放顺序上故意放在了I开头的内部类的上面,同样为了消除字母表顺序带来的影响
- 目的:看看是按照字节码顺序,还是字母表顺序呢?
- PInnerConfig里面的@Bean实例为Son,字母表顺序是三者中最为靠后的,但字节码却在中间,这样也能够消除影响
运行程序,结果输出:
InnerConfig init...
PInnerConfig init...
OuterConfig init...
Daughter init...
son init...
Parent init...
结果细节:外部类貌似总是滞后于内部类初始化;同一类的多个内部类之间顺序是按照字母表顺序(自然排序)初始化而非字节码顺序;@Bean方法的顺序依照了类的顺序
请留意本结果和上面结果是否有区别,你应该若有所思。
这是单.java文件的case(所有static类都在同一个.java文件内),接下来我在同目录下增加 2个.java文件(请自行留意类名第一个字母,我将不再赘述我的设计意图):
// 文件一:
@Configuration
class A_OuterConfig {
A_OuterConfig() {
System.out.println("A_OuterConfig init...");
}
@Bean
String a_o_bean(){
System.out.println("A_OuterConfig a_o_bean init...");
return new String();
}
@Configuration
private static class PInnerConfig {
PInnerConfig() {
System.out.println("A_OuterConfig PInnerConfig init...");
}
@Bean
String a_p_bean(){
System.out.println("A_OuterConfig a_p_bean init...");
return new String();
}
}
@Configuration
private static class InnerConfig {
InnerConfig() {
System.out.println("A_OuterConfig InnerConfig init...");
}
@Bean
String a_i_bean(){
System.out.println("A_OuterConfig a_i_bean init...");
return new String();
}
}
}
// 文件二:
@Configuration
class Z_OuterConfig {
Z_OuterConfig() {
System.out.println("Z_OuterConfig init...");
}
@Bean
String z_o_bean(){
System.out.println("Z_OuterConfig z_o_bean init...");
return new String();
}
@Configuration
private static class PInnerConfig {
PInnerConfig() {
System.out.println("Z_OuterConfig PInnerConfig init...");
}
@Bean
String z_p_bean(){
System.out.println("Z_OuterConfig z_p_bean init...");
return new String();
}
}
@Configuration
private static class InnerConfig {
InnerConfig() {
System.out.println("Z_OuterConfig InnerConfig init...");
}
@Bean
String z_i_bean(){
System.out.println("Z_OuterConfig z_i_bean init...");
return new String();
}
}
}
运行程序,结果输出:
A_OuterConfig InnerConfig init...
A_OuterConfig PInnerConfig init...
A_OuterConfig init...
InnerConfig init...
PInnerConfig init...
OuterConfig init...
Z_OuterConfig InnerConfig init...
Z_OuterConfig PInnerConfig init...
Z_OuterConfig init...
A_OuterConfig a_i_bean init...
A_OuterConfig a_p_bean init...
A_OuterConfig a_o_bean init...
Daughter init...
son init...
Parent init...
Z_OuterConfig z_i_bean init...
Z_OuterConfig z_p_bean init...
Z_OuterConfig z_o_bean init...
这个结果大而全,是有说服力的,通过这几个示例可以总结出如下结论:
- 垮.java文件 (垮配置类)之间的顺序,是由自然顺序来保证的(字母表顺序)
- 如上:下加载A打头的配置类(含静态内部类),再是O打头的,再是Z打头的
- 同一.java文件内部,static静态内部类优先于外部类初始化。若有多个静态内部类,那么按照类名自然排序初始化(并非按照定义顺序哦,请务必注意)
- 说明:一般内部类只可能与外部类“发生关系”,与兄弟之间不建议有任何联系,否则顺序控制上你就得当心了。毕竟靠自然顺序去保证是一种弱保证,容错性太低
- 同一.java文件内,不同类内的@Bean方法之间的执行顺序,保持同2一致(也就说你的@Bean所在的@Configuration配置类先加载,那你就优先被初始化喽)
- 同一Class内多个@Bean方法的执行顺序,上篇文章static关键字真能提高Bean的优先级吗?答:真能 就已经说过了哈,请移步参见
总的来说,当static标注在class类上时,在同.java文件内它是能够提升优先级的,这对于Spring Boot
的自动配置非常有意义,主要体现在如下两个方法:
- static静态内部类配置优先于外部类加载,从而静态内部类里面的@Bean也优先于外部类的@Bean先加载
- 既然这样,那么Spring Boot自动配置就可以结合此特性,就可以进行具有优先级的
@Conditional
条件判断了。这里我举个官方的例子,你便能感受到它的魅力所在:
@Configuration
public class FeignClientsConfiguration {
...
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}
因为HystrixFeign.builder()
它属于静态内部类,所以这个@Bean肯定是优先于外部的Feign.builder()
先加载的。所以这段逻辑可解释为:优先使用HystrixFeign.builder()
(若条件满足),否则使用Feign.builder().retryer(retryer)
作为兜底。通过此例你应该再一次感受到Bean的加载顺序之于Spring应用的重要性,特别在Spring Boot/Cloud下此特性尤为凸显。
你以为记住这几个结论就完事了?不,这明显不符合A哥的逼格嘛,下面我们就来继续挖一挖吧。
源码分析
关于@Configuration
配置类的顺序问题,事前需强调两点:
- 不同 .java文件 之间的加载顺序是不重要的,Spring官方也强烈建议使用者不要去依赖这种顺序
- 因为无状态性,因此你在使用过程中可以认为垮
@Configuration
文件之前的初始化顺序是不确定的
- 因为无状态性,因此你在使用过程中可以认为垮
- 同一.javaw文件内也可能存在多个
@Configuration
配置类(比如静态内部类、普通内部类等),它们之间的顺序是我们需要关心的,并且需要强依赖于这个顺序编程(比如Spring Boot)
@Configuration
配置类只有是被@ComponentScan
扫描进来(或者被Spring Boot自动配置加载进来)才需要讨论顺序(倘若是构建上下文时自己手动指好的,那顺序就已经定死了嘛),实际开发中的配置类也确实是酱紫的,一般都是通过扫描被加载。接下来我们看看@ComponentScan
是如何扫描的,把此注解的解析步骤(伪代码)展示如下:
说明:本文并不会着重分析@ComponentScan它的解析原理,只关注本文“感兴趣”部分
1、解析配置类上的@ComponentScan
注解(们):本例中TestSpring
作为扫描入口,会扫描到A_OuterConfig/OuterConfig等配置类们
ConfigurationClassParser#doProcessConfigurationClass:
// **最先判断** 该配置类是否有成员类(普通内部类)
// 若存在普通内部类,最先把普通内部类给解析喽(注意,不是静态内部类)
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
processMemberClasses(configClass, sourceClass);
}
...
// 遍历该配置类上所有的@ComponentScan注解
// 使用ComponentScanAnnotationParser一个个解析
for (AnnotationAttributes componentScan : componentScans) {
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan,...);
// 继续判断扫描到的bd是否是配置类,递归调用
...
}
细节说明:关于最先解析内部类时需要特别注意,Spring通过
sourceClass.getMemberClasses()
来获取内部类们:只有普通内部类属于这个,static静态内部类并不属于它,这点很重要哦
2、解析该注解上的basePackages/basePackageClasses等属性值得到一些扫描的基包,委托给ClassPathBeanDefinitionScanner去完成扫描
ComponentScanAnnotationParser#parse
// 使用ClassPathBeanDefinitionScanner扫描,基于类路径哦
scanner.doScan(StringUtils.toStringArray(basePackages));
3、遍历每个基包,从文件系统中定位到资源,把符合条件的Spring组件(强调:这里只指外部@Configuration配置类,还没涉及到里面的@Bean这些)注册到BeanDefinitionRegistry注册中心
ComponentScanAnnotationParser#doScan
for (String basePackage : basePackages) {
// 这个方法是本文最需要关注的方法
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
...
// 把该配置**类**(并非@Bean方法)注册到注册中心
registerBeanDefinition(definitionHolder, this.registry);
}
}
到这一步就完成了Bean定义的注册,此处可以验证一个结论:多个配置类之间,谁先被扫描到,就先注册谁,对应的就是谁最先被初始化。那么这个顺序到底是咋样界定的呢?那么就要来到这中间最为重要(本文最关心)的一步喽:findCandidateComponents(basePackage)
。
说明:Spring 5.0开始增加了
@Indexed
注解为云原生做了准备,可以让scan扫描动作在编译期就完成,但这项技术还不成熟,暂时几乎无人使用,因此本文仍旧只关注经典模式的实现
ClassPathScanningCandidateComponentProvider#scanCandidateComponents
// 最终返回的候选组件们
Set<BeanDefinition> candidates = new LinkedHashSet<>();
// 得到文件系统的路径,比如本例为classpath*:com/yourbatman/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 从文件系统去加载Resource资源文件进来
// 这里Resource代表的是一个本地资源:存在你硬盘上的.class文件
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
for (Resource resource : resources) {
if (isCandidateComponent(metadataReader)) {
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
}
}
这段代码的信息量是很大的,分解为如下两大步:
- 通过ResourcePatternResolver从磁盘里加载到所有的 .class资源Resource[]。这里面顺序信息就出现了,加载磁盘Resource资源的过程很复杂,总而言之它依赖于你os文件系统。所以关于资源的顺序可简单理解为:你磁盘文件里是啥顺序它就按啥顺序加载进来
注意:不是看.java源代码顺序,也不是看你
target
目录下的文件顺序(该目录是经过了IDEA反编译的结果,无法反应真实顺序),而是编译后看你的磁盘上的.class文件的文件顺序
- 遍历每一个Resource资源,并不是每个资源都会成为candidates候选,它有个双重过滤(对应两个isCandidateComponent()方法):
- 过滤一:使用TypeFilter执行过滤,看看是否被排除;再看看是否满足
@Conditional
条件 - 过滤二:它有两种case能满足条件(任意满足一个case即可)
isIndependent()
是独立类(top-level类 or 静态内部类属于独立类) 并且 isConcrete()是具体的(非接口非抽象类)isAbstract()
是抽象类 并且 类内存在标注有@Lookup
注解的方法
- 过滤一:使用TypeFilter执行过滤,看看是否被排除;再看看是否满足
基于以上例子,磁盘中的.class文件情况如下:
看着这个顺序,再结合上面的打印结果,是不是感觉得到了解释呢?既然@Configuration类(外部类和内部类)的顺序确定了,那么@Bean就跟着定了喽,因为毕竟配置类也得遍历一个一个去执行嘛(有依赖关系的case除外)。
特别说明:理论上不同的操作系统(如windows和Linux)它们的文件系统是有差异的,对文件存放的顺序是可能不同的(比如$xxx内部类可能放在后面),但现实状况它们是一样的,因此各位同学对此无需担心跨平台问题哈,这由JVM底层来给你保证。
什么,关于此解析步骤你想要张流程图?好吧,你知道的,这个A哥会放到本专栏的总结篇里统一供以你白嫖,关注我公众号吧~
静态内部类在容器内的beanName是什么?
看到这个截图你就懂了:在不同.java文件内,静态内部类是不用担心重名问题的,这不也就是内聚性的一种体现麽。
说明:beanName的生成其实和你注册Bean的方式有关,比如@Import、Scan方式是不一样的,这里就不展开讨论了,知道有这个差异就成。
进阶:Spring下普通内部类表现如何?
我们知道,从内聚性上来说,普通内部类似乎也可以达到目的。但是相较于静态内部类在Spring容器内对优先级的问题,它的表现可就没这么好喽。基于以上例子,把所有的static关键字去掉,就是本处需要的case。
reRun测试程序,结果输出:
A_OuterConfig init...
OuterConfig init...
Z_OuterConfig init...
A_OuterConfig InnerConfig init...
A_OuterConfig a_i_bean init...
A_OuterConfig PInnerConfig init...
A_OuterConfig a_p_bean init...
A_OuterConfig a_o_bean init...
InnerConfig init...
Daughter init...
PInnerConfig init...
son init...
Parent init...
Z_OuterConfig InnerConfig init...
Z_OuterConfig z_i_bean init...
Z_OuterConfig PInnerConfig init...
Z_OuterConfig z_p_bean init...
Z_OuterConfig z_o_bean init...
对于这个结果A哥不用再做详尽分析了,看似比较复杂其实有了上面的分析还是比较容易理解的。主要有如下两点需要注意:
- 普通内部类它不是一个独立的类(也就是说
isIndependent() = false
),所以它并不能像静态内部类那样预先就被扫描进去,如图结果展示:
- 普通内部类初始化之前,一定得先初始化外部类,所以类本身的优先级是低于外部类的(不包含@Bean方法哦)
- 普通内部类属于外部类的memberClasses,因此它会在解析当前外部类的第一步
processMemberClasses()
时被解析 - 普通内部类的beanName和静态内部类是有差异的,如下截图:
思考题:
请思考:为何使用普通内部类得到的是这个结果呢?建议copy我的demo,自行走一遍流程,多动手总是好的
总结
本文一如既往的很干哈。写本文的原动力是因为真的太多小伙伴在看Spring Boot自动配置类的时候,无法理解为毛它有些@Bean配置要单独写在一个static静态类里面,感觉挺费事;方法前直接价格static不香吗?通过这篇文章 + 上篇文章的解读,相信A哥已经给了你答案了。
static关键字在Spring中使用的这个专栏,下篇将进入到可能是你更关心的一个话题:为毛static字段不能使用@Autowired注入的分析,下篇见~
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类的更多相关文章
- static 关键字分析
在java中static 关键字用途很广,可以修饰成员变量 方法 甚至类(静态内部类),这里不分析static 修饰类 static修饰的内容的运行顺序 java的程序执行之前有一个类的加载的过程,在 ...
- Spring Boot:定制自己的starter
在学习Spring Boot的过程中,接触最多的就是starter.可以认为starter是一种服务——使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boo ...
- 干货分享:ASP.NET CORE(C#)与Spring Boot MVC(JAVA)异曲同工的编程方式总结
目录 C# VS JAVA 基础语法类比篇: 一.匿名类 二.类型初始化 三.委托(方法引用) 四.Lambda表达式 五.泛型 六.自动释放 七.重写(override) ASP.NET CORE ...
- Spring Boot实战之定制自己的starter
本文首发于个人网站,原文地址:http://www.javaadu.online/?p=535,如需转载,请注明出处 在学习Spring Boot的过程中,接触最多的就是starter.可以认为sta ...
- Spring boot与Spring cloud之间的关系
Spring boot 是 Spring 的一套快速配置脚手架,可以基于spring boot 快速开发单个微服务,Spring Boot,看名字就知道是Spring的引导,就是用于启动Spring的 ...
- 【转】Spring Boot 构建应用——快速构建 Spring Boot 应用
Spring Boot 简化了 Spring 应用开发,不需要配置就能运行 Spring 应用,Spring Boot 的自动配置是通过 Spring 4.x 的条件注解 @Conditional 来 ...
- 【Spring Boot】构造、访问Restful Webservice与定时任务
Spring Boot Guides Examples(1~3) 参考网址:https://spring.io/guides 创建一个RESTful Web Service 使用Eclipse 创建一 ...
- Spring Boot 集成 Mybatis 实现双数据源
这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离. 添加依赖 加入Mybatis启动器,这里添加了D ...
- 【spring】Spring Boot:定制自己的starter
概念 在学习Spring Boot的过程中,接触最多的就是starter.可以认为starter是一种服务——使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring ...
随机推荐
- 使用matlab进行图像处理的一些常用操作和tip
本人还是习惯使用Python语言,有时候不得不使用matlab的时候就变得举步维艰,下面记录一下使用matlab进行图像处理的一些常用操作以及代码,方便之后查阅: 1. 图像的读取 %% 读取原图像 ...
- LaTeX常用符号(持续更新)
参考网址:https://qianwenma.cn/2018/05/17/mathjax-yu-fa-can-kao/# 基本运算 1.乘法$x\times y$ x\times y 2.乘法$x^{ ...
- 2020 最新 Kubernetes实战指南
1.Kubernetes带来的变革 对于开发人员 由于公司业务多,开发环境.测试环境.预生产环境和生产环境都是隔离的,而且除了生产环境,为了节省成本,其他环境可能是没有日志收集的,在没有用k8s的 ...
- java小项目——抽奖系统
来了来了!这不又到考试周了吗!愁人,又得复习,复习,复习!这段时间每天都在复习线代和高数!(说是复习,说实话其实是在预习,啊哈哈哈哈哈),得有一段时间都没有学到新的知识了,代码感觉都生疏了,惆怅.博客 ...
- 专家解读:利用Angular项目与数据库融合实例
摘要:面对如何在现有的低版本的框架服务上,运行新版本的前端服务问题,华为云前端推出了一种融合方案,该方案能让独立的Angular项目整体运行在低版本的框架服务上,通过各种适配手段,让Angular项目 ...
- skywalking的核心概念
在 SkyWalking 中,TraceSegment 是一个介于 Trace 与 Span 之间的概念,它是一条 Trace 的一段,可以包含多个 Span.在微服务架构中,一个请求基本都会涉及跨进 ...
- redis高级命令2
主服务负责数据的写,从服务器负责客户端的高并发来读 创建主从复制 clone不能让上面的mac地址不能重复,IP地址也不能重复 122和123是从服务器,我们修改二者的配置文件 其中 192.168. ...
- vue环境配置脚手架搭建,生命周期,钩子
Vue项目环境搭建 """ node ~~ python:node是用c++编写用来运行js代码的 npm(cnpm) ~~ pip:npm是一个终端应用商城,可以换国内 ...
- Day10-微信小程序实战-交友小程序-创建friendList字段实现好友关系(添加好友功能)--内附代码
回顾:之前我们进行了删除的功能,以及对message消息的增删,下面实现添加好友的功能 我们先在数据库中,在message这个字段的list里面,添加上测试号的id,就是模拟这个两个测试号要加我主号的 ...
- SqlServer2016 startengine错误的解决方式整理
因为某些需要,最近在安装SqlServer2016,但总是安装失败,按照网上各路大佬的解决方案都没有成功.报错提示为两个:无法获取数据库引擎句柄,无法恢复数据库引擎服务.按照网上做法,使用admini ...