[spring] spring core - 配置注入及其他内容补充
上篇 [sping] spring core - 依赖注入
这里主要补一些 core 相关内容补充,同时添加了 java config bean 的方法
java config bean 是除了 XML、java 注解之外另一给实现 DI 的方法
java config bean
这个方法不使用 annotation,而是使用 @Configure
类实现
首先实现一个不使用注解的类:
package com.example.demo;public class Lambda implements DBConnection{@Overridepublic String connect() {return "Connection via Lambda";}
}
将这个类注入到 controller 的过程为:
-
创建
@Configure
类package com.example.demo;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class DBConfig { }
-
定义
@Bean
方法去配置 bean@Configuration public class DBConfig {@Beanpublic DBConnection lambdaConn() {return new Lambda();} }
bean id 默认为方法名
-
将 bean 注入到 controller
这里依旧使用
@Qualifier("lambdaConn")
去实现:@RestController public class FirstRestController {private DBConnection dbconn;@Autowiredpublic void setDBconn(@Qualifier("lambdaConn") DBConnection dbconn) {System.out.println("dbconn from setter");this.dbconn = dbconn;}}
效果如下:
java config bean vs annotation
二者的优缺点还是比较明显的
方法 | 优点 | 缺点 |
---|---|---|
注解 |
|
|
java-based-config |
|
|
一般推荐方案有:
- 中小型项目,代码量较少的情况下,使用注解
- 中大型项目使用 java 配置
- 在有需求的时候进行混用,特别是对于无法控制的第三方库,或是一时半会儿没法重构的代码
- 考虑使用 spring profile
@Profile
代替 java config
component scan
关于 component scan,spring 会扫面 entry point——即有 @SpringBootApplication
的 java class——的统计和子 package,如果结构更新成下面这样的:
那 util
中的 class 不会被 spring 自动扫到:
想要让 spring 扫到 util 下的代码,需要使用在 @SpringBootApplication
中添加 scanBasePackages
,如:
@SpringBootApplication(scanBasePackages = {"com.example.util"})
public class DemoApplication {// ...
}
除了有 scanBasePackages
之外,还有 scanBasePackageClasses
懒初始化
在 spring 项目启动时,所有的 bean 默认都完成初始化。我在构造函数里面添加了一行 System.out.println("In constructor: " + getClass().getSimpleName());
后,终端显示如下:
可以看到,在 LiveReload 和 Tomcat 启动之前,所有的 bean 都完成了实例化。
这也就意味着项目在启动初期一定会更加耗时,想要对其优化,可以使用懒初始化(lazy initialization)。这样 bean 只有在这两个情况下会被初始化:
- 被其他的 bean 进行调用
- 直接请求当前 bean
实现的方法有三种:
-
全局化实现
这个在 spring boot 中的配置文件,如
application.properties
中实现:spring.main.lazy-initialization=true
-
使用
@Lazy
注解这个又可以分成多种情况:
-
直接使用
@Component
的,可以在@Component
中加:@Component@Lazypublic class MySQL implements DBConnection {}
这时候 MySQL 就不会显示完成加载:
-
Java Config 中可以在对应的 bean 上加,如:
@Configurationpublic class DBConfig {@Bean@Lazypublic DBConnection lambdaConn() {return new Lambda();}@Bean@Lazypublic DBConnection mongoDBConn() {return new MongoDB();}}
我这里添加了一个 MongoDB,因为
lambdaConn
在启动 rest controller 时被请求,所以肯定是要实例化才能进行下一步的。改完后效果如下:
-
在被
@Autowired
地方的添加@Lazy
@RestController public class FirstRestController {private DBConnection dbconn;@Autowired@Lazypublic void setDBconn(@Qualifier("lambdaConn") DBConnection dbconn) {System.out.println("dbconn from setter");this.dbconn = dbconn;}}
这个情况下,只有当该 rest controller 被访问时,才会进行初始化:
JDBC 是唯一一个没有被添加
@Lazy
的类,因此一开始它就被初始化了随后可以看到,尽管这里用的是 setter 注入,但是却没有对象被实例化,一直到服务器启动了,网页被访问之后,Lambda 对象才被实例化
-
-
XML 中也可以添加
lazy-init
属性实现,不过我这没 XML 的案例,就不贴代码了
总体来说,懒初始化可以提升项目启动速度,不过这个成本可能会加到运行的时候,因此在做优化时还是要跑一些 metrics 去最终决定是否要实现懒初始化,或是哪些 bean 被懒初始
bean 生命周期
上网找了一下,大体的流程是这样的:
不过教程没有细说,之时提了两个比较常用的 lifecycle callback:init 和 cleanup。前者在 bean 准备被使用前调用,这里可以处理一些 side effect,如链接 db,新建 socket 等,后者则是在 bean 被销毁前调用,用来清理一些 side effect
init
有三种方式可以实现:
-
XML
<bean id="someBeanId" class="some.class" init-method="method-name" />
-
注解
public class MySQL implements DBConnection {@PostConstructpublic void init() {// init logic} }
-
通过实现
InitializingBean
进行重载public class MySQL implements DBConnection, InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {} }
cleanup
也是三种实现方式
- XML
<bean id="someBeanId" class="some.class" destroy-method="method-name" />
-
注解
public class MySQL implements DBConnection {@PreDestroypublic void cleanup() {// clean up logic} }
-
实现 interface
public class MySQL implements DBConnection, DisposableBean {@Overridepublic void destroy() throws Exception {} }
bean scope
bean scope 和它的生命周期,以及在 app 中的可见度(即被分享)有关,所有的 bean 默认都是 singleton。
目前官方提供的列表为:
scope | 描述 |
---|---|
singleton | 默认,每个 IoC 容器只会生成一个 |
prototype | 每次容器新创建一个对象,就就会新创建一个 instance |
request | 每个 HTTP 请求会生成一个新的 instance 只有在 web 项目中适用 |
session | 每个 HTTP session 中会生成一个 instance 只有在 web 项目中适用 |
application | 每个 ServletContext 生命周期会生成一个 instance 只有在 web 项目中适用 网页项目中与 singleton 相似 |
websocket | 同理,一个 websocket 生命周期中会生成一个 instance 只有在 web 项目中适用 |
XML 中的配置方法为:
<bean id="someBeanId" class="some.class" scope="a_valid_scope" />
使用 annotation 的方式为:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Lambda implements DBConnection{// ...
}
这里简单的进行一下 shallow compare,即对比地址:
public class FirstRestController {private DBConnection dbconn;private DBConnection dbconn2;@Autowiredpublic void setDBconn(@Qualifier("lambdaConn") DBConnection dbconn, @Qualifier("lambdaCoÏnn") DBConnection dbconn2) {System.out.println("dbconn from setter");this.dbconn = dbconn;this.dbconn2 = dbconn2;}@GetMapping("/check")public String getCheck() {System.out.println(dbconn.getClass().getSimpleName());return "Comparing beans: dbconn" + (this.dbconn2 == this.dbconn ? "==" : "!=") + " dbconn2.";}
}
在 prototype 的情况下为 false:
在默认(singleton)的情况下为 true:
特殊的 prototype
prototype 的清理有一些特殊,因为 spring 不会对 prototype 的生命周期进行完整的干里,即不会调用对应的清理函数,所以如果某个 bean 需要经常被创建/毁灭,那么就要考虑是不是需要使用 prototype,还是应该使用其他的 scope
以及,prototype bean 默认就是懒初始化,所以没有必要添加 @Lazy