Spring 优雅注册 Bean 的方式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 优雅注册 Bean 的方式相关的知识,希望对你有一定的参考价值。
参考技术A这篇先说明用法,下篇分析以下场景是如何将 Bean 注册进 IOC容器的。
这种用法在项目中是非常常见的,基本上是必有。我们来看下用法:
这样一个 Bean 就注册进 IOC 容器了,Bean 的名称默认是方法名,并且是不会转换大小写的,也就是假如你的方法名是 TestBean() ,那么 Bean 的名称就是 TestBean 。当然我们也可以使用 name 或者 value 指定 Bean 的名称,比如 @Bean(value = "testBean"),如果二者同时存在则会报错。
我们来看下其他属性:
autowireCandidate:默认值是 true 。如果设置为 false 的话,那么通过 byType 的方式获取 Bean 就会报错,当然我们可以使用 Resource 注解获取。
initMethod:在 Bean 实例化后调用的初始化方法,值是 Bean 类中的方法名。
destroyMethod:在 Bean 要销毁时调用的清理方法,值是 Bean 类中的方法名。
@Bean 注解只能定义在 @Configuration 类下吗? NO NO NO,它可以定义在任意能被 IOC 扫描的注解下,比如 @Component注解,至于区别,下篇再讲。
先讲普通用法:
深度用法:
ComponentScan 注解中有两个这样的属性:includeFilters 与 excludeFilters,前一个是只包含规则,后一个是排除包含规则,他们的值是一个 @Filter 注解的形式,Filter 中的 type 有 5 中类型,分别如下。
1、ANNOTATION
第一种是以注解的形式包含或不包含,比如:
这里边要配置useDefaultFilters = false 禁用默认规则,因为默认规则是扫描所有,配只包含就没用了。这里的意思只扫描 Configuration 注解。
2、ASSIGNABLE_TYPE
这种是包含我们给定的类型,不管是给定的类型和子类都会被包含进 IOC 容器。
然后我们发现 testBean 注册进去了,为什么我们不标注 @Component 这样的注解实例也会被注册进 IOC 呢?因为 ComponentScan 会扫描包下所有文件,只要符合我们定义的过滤规则,它就会将 Bean 注册进 IOC 容器中。
3、ASPECTJ
ASPECTJ 是使用 aspectj 表达式
4、REGEX
REGEX 是使用正则表达式
5、CUSTOM
这种呢就是我们 SpringBootApplication 注解用到的方式了,我来解释一下具体规则:这种方式是可以自己自定义扫描规则,它接受一个实现 TypeFilter 接口的类。
当它扫描类的时候扫描到了 TestBean,然后符合了我的匹配规则(也就是返回true)就注册进去了。
下面的例子中,我们直接看 Spring 源码的实现比较具有代表性一点。
我们点进 @EnableTransactionManagement 注解中,发现了这个 @Import(TransactionManagementConfigurationSelector.class),它的作用就是将类导入,类会被注册进 IOC 容器中。
这个注解放置的位置要是 Spring 能扫描到的地方,不然 Spring 也不会主动去解析这个注解。
如果我们自己要使用注解的话,我们可以做个类似于 EnableTransactionManagement 的功能插拔式导入配置类,这样就可以实现动态开启一些 Bean 了。
我们还是来看下 TransactionManagementConfigurationSelector 这个类,看下它的继承关系发现它间接性的实现了 ImportSelector 接口,主要看它实现的这个方法:
这个方法的作用就是根据你返回的类全限定名(org.springframework.context.annotation.AutoProxyRegistrar)数组来创建 Bean 。
实现了 ImportSelector 的类也是需要使用 @Import 导入。
这个我们来看下 @MapperScan (org.mybatis.spring.annotation)导入的 MapperScannerRegistrar 发现它实现了 ImportBeanDefinitionRegistrar:
它的作用是拿到 BeanDefinitionRegistry Bean 的定义信息,然后往里面加 BeanDefinition 就会将相应的对象注册进去,它更深入的就不说了,实际上就是解析下注解属性,然后扫描相应的包下的类注册 Bean。我们自己搞个简单的。
这样就注册了一个 Bean 名称是 testBean 类型是 TestBean 类型的 Bean 了。
如果注册的是一个有参构造器呢?那就这样:
addConstructorArgValue 根据构造器参数的顺序去添加。
实现了 ImportBeanDefinitionRegistrar 的类也是需要使用 @Import 导入。
然后 TestBean 就注册进去了,打印的时候我们发现 Bean 的名称是 MyFactoryBean 的全限定名,但是它的类型是 TestBean 类型的,如果想要获取 MyFactoryBean 类型的 Bean 的话,通过 Bean 名称为 &myFactoryBean 就能获取到。
在我们的Spring Boot项目中,一般都是只扫描主类下的所有类,然后将一些被特定注解标注的类加载到IOC容器,但是如果我们将包分离,我们又如何更加方便的将其他包的类加载进来呢? spring boot提供了一种类似于Java的SPI(服务发现)机制spring.factories,只要在resources目录下创建META-INF文件夹,再创建 spring.factories文件,然后再里面配置
这样在导入当前包的就会自动扫描spring.factories文件,解析后将里面的一些类加载到IOC容器中。具体的实现代码在spring-core的SpringFactoriesLoader类中。
这些就不讲了。
初识Spring Framework——Bean注册Bean依赖注入
目录
简述Spring容器
传统方式: new对象,然后调用对象的属性或方法 User user = new User();
spring方式:把对象存入spring容器种,用的时候从容器中取出,随后调用对象的属性或方法
注册Bean的三种方式
方式一:类注解
a)Controller —— 前端业务交互层【参数效验】
b)Service —— 业务处理的中间层【业务处理】
c)Reoisitory —— 数据持久层
d)Component —— 通用对象工具类
import org.springframework.stereotype.Controller;
@Controller
public class LoginController {
}
定义好了Bean对象,注册到容器中以后,就可以获取Bean对象了,在入口类 org.example.App 中,可以通过 ApplicationContext 对象获取Bean,有两种方式获取:
- 通过类型获取:这种获取方式要求该类型的Bean只能有一个
- 通过名称获取:同一个类型的Bean可以有多个
- 类名+名称:获取到唯一的Bean
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
LoginController loginController = (LoginController) context.getBean("loginController");
System.out.println(loginController);
//关闭容器
((ClassPathXmlApplicationContext)context).close();
}
方式二:@Bean
也可以当作方法注解。当前类被 Spring 扫描到时,可以在方法上使用 @Bean 注解,通过方法返回类型,也可以定义、注册Bean对象,默认使用方法名作为Bean的名称。
创建一个实体类
package org.example.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class User {
private String name;
private String password;
}
这里用到了 lombok 插件,用注解自动生成get、set、toString方法,它用自定义注解的方式,在代码编译的时候,将自定义的注解换成 JVM 可以执行的方法。(传统的 get、set、tostring方法)
用 @Bean 的方式使方法进行依赖注入
package org.example.controller;
import lombok.Getter;
import org.example.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Controller
@Getter
public class LoginController {
@Bean
public User user1() {
User user = new User();
user.setName("悟空");
user.setPassword("妖怪哪里跑");
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("八戒");
user.setPassword("背媳妇回家");
return user;
}
}
在 App 中调用容器中的 User 对象
package org.example;
import org.example.controller.LoginController;
import org.example.model.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.beans.Introspector;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User)context.getBean("user1");
System.out.println(user);
User user2 = context.getBean("user2",User.class);
System.out.println(user2);
//关闭容器
((ClassPathXmlApplicationContext)context).close();
}
}
方式三:@Configuration
在类被Spring扫描到时,使用 @Configuration 注解,可以注册一个配置类到容器中。
package org.example.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyApplication {
}
对象注入的三种方式(从spring获取对象)
方式一:@Autowired 属性注入
在服务层添加 LoginService 对象,并提供 sayHi 方法
import org.springframework.stereotype.Service;
//存储
@Service
public class LoginService {
public void sayHi() {
System.out.println("Login-sayhi!");
}
}
在控制层添加 LoginService 对象,然后调用该对象的 sayHi 方法
@Controller
@Getter
public class LoginController {
//第一种注入(查询)方式:属性注入
@Autowired
private LoginService loginService;
public void sayHi() {
//参数效验
loginService.sayHi();
}
}
最后在主方法内调用 loginController 对象即可
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
LoginController loginController = context.getBean(LoginController.class);
loginController.sayHi();
//关闭容器
((ClassPathXmlApplicationContext)context).close();
}
}
正常的执行了 sayHi 方法
ps:第一种方式属性名称,注入有多个对象的类的对象时,名称不能够随便起,会导致无法识别
如果实在想起别名,那么可以添加 @qualifier 注释来起别名
private User u1;
@Autowired
@Qualifier("user1")
public void setU1(User u1) {
this.u1 = u1;
}
方式二:通过 Set 的方式注入
与第一种不同的是,此时的 @Autowired 修饰的是 set 方法,通过 set 方法来注入对象
(该方式参数变量名可随意取)
@Controller
@Getter
public class LoginController {
//第二种注入的方式:通过 Set 的方式注入
private LoginService loginService;
@Autowired
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
public void sayHi() {
//参数效验
loginService.sayHi();
}
}
方式三:构造方法注入
此方法加不加 @Autowired 都可以正常执行,但建议加上
@Controller
@Getter
public class LoginController {
//第三种注入的方式:构造函数注入
private LoginService loginService;
@Autowired
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
public void sayHi() {
//参数效验
loginService.sayHi();
}
}
Spring 各层之间联系
Bean
Bean 其实就是对象,只不过在 Spring 种叫作 Bean
Bean 的作用域(Bean的类型)
单例 (singleton)
描述:该作用域下的Bean在IoC容器中只存在一个实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是同一个对象。
场景:通常无状态的Bean使用该作用域。无状态表示Bean对象的属性状态不需要更新
备注:Spring默认选择该作用域
创建新对象 (prototype)
描述:每次对该作用域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是新的对象实例。
场景:通常有状态的Bean使用该作用域
request
描述:每次http请求会创建新的Bean实例,类似于prototype
场景:一次http的请求和响应的共享Bean
备注:限定SpringMVC中使用
session
描述:在一个http session中,定义一个Bean实例
场景:用户回话的共享Bean, 比如:记录一个用户的登陆信息
备注:限定SpringMVC中使用
application
描述:在一个http servlet Context中,定义一个Bean实例
场景:Web应用的上下文信息,比如:记录一个应用的共享信息
备注:限定SpringMVC中使用
websocket
描述:在一个HTTP WebSocket的生命周期中,定义一个Bean实例
场景:WebSocket的每次会话中,保存了一个Map结构的头信息,将用来包裹客户端消息头。第一次初始化后,直到WebSocket结束都是同一个Bean。
备注:限定Spring WebSocket中使用
Bean 的生命周期
总结 Bean 生命周期
主要步骤为:
- 实例化Bean:通过反射调用构造方法实例化对象。
- 依赖注入:装配Bean的属性
- 实现了Aware接口的Bean,执行接口方法:如顺序执行BeanNameAware、BeanFactoryAware、ApplicationContextAware的接口方法。
- Bean对象初始化前,循环调用实现了BeanPostProcessor接口的预初始化方法(postProcessBeforeInitialization)
- Bean对象初始化:顺序执行@PostConstruct注解方法、InitializingBean接口方法、init-method方法
- Bean对象初始化后,循环调用实现了BeanPostProcessor接口的后初始化方法(postProcessAfterInitialization)
- 容器关闭时,执行Bean对象的销毁方法,顺序是:@PreDestroy注解方法、DisposableBean接口方法、destroy-method
ps:第一步的实例化是指new对象,Spring的语义中说初始化Bean包含Bean生命周期中的初始化步骤
以上是关于Spring 优雅注册 Bean 的方式的主要内容,如果未能解决你的问题,请参考以下文章