上篇 [sping] spring core - 依赖注入
这里主要补一些 core 相关内容补充,同时添加了 java config bean 的方法
java config bean 是除了 XML、java 注解之外另一给实现 DI 的方法
这个方法不使用 annotation,而是使用 @Configure
类实现
首先实现一个不使用注解的类:
package com.example.demo;
public class Lambda implements DBConnection{
@Override
public 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 {
@Bean
public DBConnection lambdaConn() {
return new Lambda();
}
}
bean id 默认为方法名
将 bean 注入到 controller
这里依旧使用 @Qualifier("lambdaConn")
去实现:
@RestController
public class FirstRestController {
private DBConnection dbconn;
@Autowired
public void setDBconn(@Qualifier("lambdaConn") DBConnection dbconn) {
System.out.println("dbconn from setter");
this.dbconn = dbconn;
}
}
效果如下:
二者的优缺点还是比较明显的
方法 | 优点 | 缺点 |
---|---|---|
注解 |
|
|
java-based-config |
|
|
一般推荐方案有:
@Profile
代替 java config关于 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 只有在这两个情况下会被初始化:
实现的方法有三种:
全局化实现
这个在 spring boot 中的配置文件,如 application.properties
中实现:
spring.main.lazy-initialization=true
使用 @Lazy
注解
这个又可以分成多种情况:
直接使用 @Component
的,可以在 @Component
中加:
@Component
@Lazy
public class MySQL implements DBConnection {
}
这时候 MySQL 就不会显示完成加载:
Java Config 中可以在对应的 bean 上加,如:
@Configuration
public class DBConfig {
@Bean
@Lazy
public DBConnection lambdaConn() {
return new Lambda();
}
@Bean
@Lazy
public DBConnection mongoDBConn() {
return new MongoDB();
}
}
我这里添加了一个 MongoDB,因为 lambdaConn
在启动 rest controller 时被请求,所以肯定是要实例化才能进行下一步的。
改完后效果如下:
在被 @Autowired
地方的添加 @Lazy
@RestController
public class FirstRestController {
private DBConnection dbconn;
@Autowired
@Lazy
public 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 被懒初始
上网找了一下,大体的流程是这样的:
不过教程没有细说,之时提了两个比较常用的 lifecycle callback:init 和 cleanup。前者在 bean 准备被使用前调用,这里可以处理一些 side effect,如链接 db,新建 socket 等,后者则是在 bean 被销毁前调用,用来清理一些 side effect
有三种方式可以实现:
XML
<bean id="someBeanId" class="some.class" init-method="method-name" />
注解
public class MySQL implements DBConnection {
@PostConstruct
public void init() {
// init logic
}
}
通过实现 InitializingBean
进行重载
public class MySQL implements DBConnection, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
}
}
也是三种实现方式
<bean id="someBeanId" class="some.class" destroy-method="method-name" />
注解
public class MySQL implements DBConnection {
@PreDestroy
public void cleanup() {
// clean up logic
}
}
实现 interface
public class MySQL implements DBConnection, DisposableBean {
@Override
public void destroy() throws Exception {
}
}
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;
@Autowired
public 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 的清理有一些特殊,因为 spring 不会对 prototype 的生命周期进行完整的干里,即不会调用对应的清理函数,所以如果某个 bean 需要经常被创建/毁灭,那么就要考虑是不是需要使用 prototype,还是应该使用其他的 scope
以及,prototype bean 默认就是懒初始化,所以没有必要添加 @Lazy