static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(推荐)

生命太短暂,不要去做一些根本没有人想要的东西。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。

前言

各位小伙伴大家好,我是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();
 }
 }
}

我先解释下我这么做的意图:

  1. 增加一个字母P开头的内部类,自然顺序P在O(外部类)后面,消除影响
  2. P开头的内部类在源码摆放顺序上故意放在了I开头的内部类的上面,同样为了消除字母表顺序带来的影响
  3. 目的:看看是按照字节码顺序,还是字母表顺序呢?
  4. 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);
			}
		}
	}

这段代码的信息量是很大的,分解为如下两大步:

1.通过ResourcePatternResolver从磁盘里加载到所有的 .class资源Resource[]。这里面顺序信息就出现了,加载磁盘Resource资源的过程很复杂,总而言之它依赖于你os文件系统。所以关于资源的顺序可简单理解为:你磁盘文件里是啥顺序它就按啥顺序加载进来

注意:不是看.java源代码顺序,也不是看你target目录下的文件顺序(该目录是经过了IDEA反编译的结果,无法反应真实顺序),而是编译后看你的磁盘上的.class文件的文件顺序

2.遍历每一个Resource资源,并不是每个资源都会成为candidates候选,它有个双重过滤(对应两个isCandidateComponent()方法):

过滤一:使用TypeFilter执行过滤,看看是否被排除;再看看是否满足@Conditional条件

过滤二:它有两种case能满足条件(任意满足一个case即可)

  • isIndependent()是独立类(top-level类 or 静态内部类属于独立类) 并且 isConcrete()是具体的(非接口非抽象类)
  • isAbstract()是抽象类 并且 类内存在标注有@Lookup注解的方法

基于以上例子,磁盘中的.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 Boot搞出那么多静态内部类(推荐)的文章就介绍到这了,更多相关Spring Boot静态内部类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java之static关键字用法实例解析

    本文实例讲述了java中static关键字用法,分享给大家供大家参考.具体分析如下: 一.介绍: 1.在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量,在第一次使用时被初始化,对于该类的所有对象来说,static成员变量只有一份. 2.用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static成员.(静态方法不再是针对于某个对象调用,所以不能访问非静态成员) 3.可以通过对象引用或类名(不需要实例化)访问静态

  • Java中static关键字的作用和用法详细介绍

    static表示"全局"或者"静态"的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念. 被static修饰的成员变量和成员方法独立于该类的任何对象.也就是说,它不依赖类特定的实例,被类的所有实例共享. 只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们.因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象. 用public修饰的static成员变量和成员方法本质是

  • 从内存地址解析Java的static关键字的作用

    静态成员变量与非静态成员变量的区别 以下面的例子为例说明 package cn.galc.test; public class Cat { /** * 静态成员变量 */ private static int sid = 0; private String name; int id; Cat(String name) { this.name = name; id = sid++; } public void info() { System.out.println("My Name is &quo

  • java 中的static关键字和final关键字的不同之处

    static 1.在类中,用static修饰的属性,称为静态属性.为这个类的所有对象所共有,存放在静态存储区,所有该类的对象都可以访问且访问的都是同一变量.可以用作计数器,来统计总共创建了多少个各类的对象. 2.在类中,用static 修饰的方法为静态方法,在静态方法中不可以访问非静态的属性和方法,但在非静态方法中可以访问静态方法和属性:且static方法多态失效,不能使用this. 3.由于静态属性和方法是属于该类的所有对象的,所以可以用类名.静态属性/方法名---来访问. 4.static

  • 浅谈Java中static关键字的作用

    static关键字主要有两种作用: 第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关. 第二,实现某个方法或属性与类而不是对象关联在一起 具体而言,在Java语言中,static主要有4中使用情况:成员变量.成员方法.代码块和内部类 (1)static成员变量: Java类提供了两种类型的变量:用static关键字修饰的静态变量和不用static关键字修饰的实例变量.静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,因此就可以被使

  • static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(推荐)

    生命太短暂,不要去做一些根本没有人想要的东西.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习. 前言 各位小伙伴大家好,我是A哥.上篇文章了解了static关键字 + @Bean方法的使用,知晓了它能够提升Bean的优先级,在@Bean方法前标注static关键字,特定情况下可以避免一些烦人的"警告"日志的输出,排除隐患让工程变得更加安全.我们知道static关键字它不仅可使用在

  • 基于spring boot 的配置参考大全(推荐)

    如下所示: # =================================================================== # COMMON SPRING BOOT PROPERTIES # # This sample file is provided as a guideline. Do NOT copy it in its # entirety to your own application. ^^^ # =============================

  • 浅谈Spring Boot 微服务项目的推荐部署方式

    如果开发过spring boot的程序,应该都知道,使用spring boot官方的maven打包插件(spring-boot-maven-plugin) 来打包,打出来的jar包一般有40M以上. 如果公司的服务器上传带宽不高,那么手动上传一个jar或者jenkins部署一次jar,都是非常痛苦的........ 但是,如果打包的时候不引入lib,那么打出来的jar包一般只有几十k而已,非常小,想怎么传就怎么传......... 本文会提供一个bash启动脚本,只需要稍做更改,即可适应你的程序

  • 利用Spring boot如何创建简单的web交互应用

    关于页面渲染 其实在工作中,一直都是前后端分离,也就是说,我的工作从来都是提供接口,而不写 html css js 之类的,所以在这方面也没有经验. 这里为了给大家介绍下模板引擎,我将会写个非常非常简单的页面,如果不好看,请见谅~ Spring Boot 官方推荐的模板引擎是 Thymeleaf ,点击可以进入其官网了解详情. 本章目标 让 Spring Boot 应用可以访问到静态资源文件 创建用户登录表单,并对用户名.密码进行校验 校验失败,将返回登录页,并展示错误信息 校验成功,重定向到苹

  • 如何在Spring Boot启动时运行定制的代码

    Spring Boot会自动为我们做很多配置,但迟早你需要做一些自定义工作.在本文中,您将学习如何挂钩应用程序引导程序生命周期并在Spring Boot启动时执行代码. 1.执行bean初始化的方法 Spring启动应用程序后运行某些逻辑的最简单方法是将代码作为所选bean引导过程的一部分来执行. 只需创建一个类,将其标记为Spring组件,并将应用程序初始化代码放在带有@PostConstruct注释的方法中.理论上,您可以使用构造函数而不是单独的方法,但将对象的构造与其实际责任分开是一种很好

  • spring boot 本地图片不能加载(图片路径)的问题及解决方法

    在使用html加载图片时,发现本地图片在页面上不能显示,但是直接引用网络上的资源是可以显示的.参考了众多前人的经验,得出一下结论: 本地图片不能显示最主要的问题是,图片在本地url和图片在服务器上被加载是的URL是不一样的.也就是路径的问题. 解决的办法其实很简单,只要写一个配置文件,也就是图片位置的转化器,原理是虚拟一个在服务器上的文件夹,与本地图片的位置进行匹配. 在调用本地图片时,就相当于调用服务器上的图片. 关键的代码如下: @Configuration public class MyW

  • Spring Boot 静态资源处理

    静态资源处理 Spring Boot 默认的处理方式就已经足够了,默认情况下Spring Boot 使用WebMvcAutoConfiguration中配置的各种属性. 建议使用Spring Boot 默认处理方式,需要自己配置的地方可以通过配置文件修改. 但是如果你想完全控制Spring MVC,你可以在@Configuration注解的配置类上增加@EnableWebMvc,增加该注解以后WebMvcAutoConfiguration中配置就不会生效,你需要自己来配置需要的每一项.这种情况下

  • ssm项目改造spring boot项目完整步骤

    目录 添加依赖 添加启动类 拷贝项目代码 配置数据库连接池 添加依赖 连接池的自动配置方式 配置 Druid 连接池 集成 MyBatis 添加依赖 配置 Mapper 对象 MyBatis配置属性 事务管理 添加依赖 注解方式 配置切换代理 测试验证 集成 Web 添加依赖 修改端口 目录结构 静态资源的一些处理 前端控制器映射路径配置 集成 Thymeleaf 添加依赖 相关配置 修改模板文件 统一异常处理 框架自带方式 控制器增强器方式 添加注册多个拦截器 系统日志 为什么要用日志 Spr

  • springboot static关键字真能提高Bean的优先级(厉害了)

    生命太短暂,不要去做一些根本没有人想要的东西.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众号[BAT的乌托邦]逐个击破,深入掌握,拒绝浅尝辄止. 前言 各位小伙伴大家好,我是A哥.关于Spring初始化Bean的顺序问题,是个老生常谈的话题了,结论可总结为一句话:全局无序,局部有序.Spring Bean整体上是无序的,而现实是大多数情况下我们真的无需关心,无序就无序呗,无所谓

  • 浅析java 的 static 关键字用法

    本篇浅析java中static的用法,主要五个方面:静态成员变量,静态方法,静态块,静态内部类,静态导包. 首先还是一张表格说一下静态对象和非静态对象的区别: 静态对象 非静态对象 归属 类共同具有 类的各个实例独立拥有 内存分配 内存空间上固定的 附属类分配 分配空间顺序 优先分配静态对象空间 优先分配静态对象空间,初始化也一样 1 静态变量,静态方法,静态块 静态对象,静态方法都是在原对象和方法上加上static关键字修饰,表示类可以直接调用这些,而不需要实例化后再调用.具有的好处是: 1-

随机推荐