【Spring Framework】12种spring中定义bean的方法
前言
在庞大的java体系中,spring有着举足轻重的地位,它给每位开发者带来了极大的便利和惊喜。我们都知道spring是创建和管理bean的工厂,它提供了多种定义bean的方式,能够满足我们日常工作中的多种业务场景。
那么问题来了,你知道spring中有哪些方式可以定义bean?
我估计很多人会说出以下三种:
没错,但我想说的是以上三种方式只是开胃小菜,实际上spring的功能远比你想象中更强大。
各位看官如果不信,请继续往下看。
1. xml文件配置bean
我们先从xml配置bean开始,它是spring最早支持的方式。后来,随着springboot越来越受欢迎,该方法目前已经用得很少了,但我建议我们还是有必要了解一下。
1.1 构造器
如果你之前有在bean.xml文件中配置过bean的经历,那么对如下的配置肯定不会陌生:
<bean id="personService" class="com.sue.cache.service.test7.PersonService">
</bean>
这种方式是以前使用最多的方式,它默认使用了无参构造器创建bean。
当然我们还可以使用有参的构造器,通过标签来完成配置。
<bean id="personService" class="com.sue.cache.service.test7.PersonService">
<constructor-arg index="0" value="susan"></constructor-arg>
<constructor-arg index="1" ref="baseInfo"></constructor-arg>
</bean>
其中:
- index表示下标,从0开始。
- value表示常量值
- ref表示引用另一个bean
1.2 setter方法
除此之外,spring还提供了另外一种思路:通过setter方法设置bean所需参数,这种方式耦合性相对较低,比有参构造器使用更为广泛。
先定义Person实体:
@Data
public class Person {
private String name;
private int age;
}
它里面包含:成员变量name和age,getter/setter方法。
然后在bean.xml文件中配置bean时,加上标签设置bean所需参数。
<bean id="person" class="com.sue.cache.service.test7.Person">
<property name="name" value="susan"></constructor-arg>
<property name="age" value="18"></constructor-arg>
</bean>
1.3 静态工厂
这种方式的关键是需要定义一个工厂类,它里面包含一个创建bean的静态方法。例如:
public class SusanBeanFactory {
public static Person createPerson(String name, int age) {
return new Person(name, age);
}
}
接下来定义Person类如下:
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person {
private String name;
private int age;
}
它里面包含:成员变量name和age,getter/setter方法,无参构造器和全参构造器。
然后在bean.xml文件中配置bean时,通过factory-method参数指定静态工厂方法,同时通过设置相关参数。
<bean class="com.sue.cache.service.test7.SusanBeanFactory" factory-method="createPerson">
<constructor-arg index="0" value="susan"></constructor-arg>
<constructor-arg index="1" value="18"></constructor-arg>
</bean>
1.4 实例工厂方法
这种方式也需要定义一个工厂类,但里面包含非静态的创建bean的方法。
public class SusanBeanFactory {
public Person createPerson(String name, int age) {
return new Person(name, age);
}
}
Person类跟上面一样,就不多说了。
然后bean.xml文件中配置bean时,需要先配置工厂bean。然后在配置实例bean时,通过factory-bean参数指定该工厂bean的引用。
<bean id="susanBeanFactory" class="com.sue.cache.service.test7.SusanBeanFactory">
</bean>
<bean factory-bean="susanBeanFactory" factory-method="createPerson">
<constructor-arg index="0" value="susan"></constructor-arg>
<constructor-arg index="1" value="18"></constructor-arg>
</bean>
1.5 FactoryBean
不知道大家有没有发现,上面的实例工厂方法每次都需要创建一个工厂类,不方面统一管理。
这时我们可以使用FactoryBean接口。
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
在它的getObject方法中可以实现我们自己的逻辑创建对象,并且在getObjectType方法中我们可以定义对象的类型。
然后在bean.xml文件中配置bean时,只需像普通的bean一样配置即可。
<bean id="userFactoryBean" class="com.sue.async.service.UserFactoryBean">
</bean>
轻松搞定,so easy。
注意:getBean("userFactoryBean");获取的是getObject方法中返回的对象。而getBean("&userFactoryBean");获取的才是真正的UserFactoryBean对象。
我们通过上面五种方式,在bean.xml文件中把bean配置好之后,spring就会自动扫描和解析相应的标签,并且帮我们创建和实例化bean,然后放入spring容器中。
虽说基于xml文件的方式配置bean,简单而且非常灵活,比较适合一些小项目。但如果遇到比较复杂的项目,则需要配置大量的bean,而且bean之间的关系错综复杂,这样久而久之会导致xml文件迅速膨胀,非常不利于bean的管理。
2. Component注解
为了解决bean太多时,xml文件过大,从而导致膨胀不好维护的问题。在spring2.5中开始支持:@Component、@Repository、@Service、@Controller等注解定义bean。
如果你有看过这些注解的源码的话,就会惊奇得发现:其实后三种注解也是@Component。
image.png
image.png
@Component系列注解的出现,给我们带来了极大的便利。我们不需要像以前那样在bean.xml文件中配置bean了,现在只用在类上加Component、Repository、Service、Controller,这四种注解中的任意一种,就能轻松完成bean的定义。
@Component系列注解的出现,给我们带来了极大的便利。我们不需要像以前那样在bean.xml文件中配置bean了,现在只用在类上加Component、Repository、Service、Controller,这四种注解中的任意一种,就能轻松完成bean的定义。
@Service
public class PersonService {
public String get() {
return "data";
}
}
不过,需要特别注意的是,通过这种@Component扫描注解的方式定义bean的前提是:需要先配置扫描路径。
目前常用的配置扫描路径的方式如下:
- 在applicationContext.xml文件中使用context:component-scan标签。例如:
<context:component-scan base-package="com.sue.cache" />
- 在springboot的启动类上加上@ComponentScan注解,例如:
@ComponentScan(basePackages = "com.sue.cache")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
}
}
- 直接在SpringBootApplication注解上加,它支持ComponentScan功能:
@SpringBootApplication(scanBasePackages = "com.sue.cache")
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
}
}
当然,如果你需要扫描的类跟springboot的入口类,在同一级或者子级的包下面,无需指定scanBasePackages参数,spring默认会从入口类的同一级或者子级的包去找。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
}
}
此外,除了上述四种@Component注解之外,springboot还增加了@RestController注解,它是一种特殊的@Controller注解,所以也是@Component注解。
@RestController还支持@ResponseBody注解的功能,即将接口响应数据的格式自动转换成json。
image.png
@Component系列注解已经让我们爱不释手了,它目前是我们日常工作中最多的定义bean的方式。
3. JavaConfig
@Component系列注解虽说使用起来非常方便,但是bean的创建过程完全交给spring容器来完成,我们没办法自己控制。
spring从3.0以后,开始支持JavaConfig的方式定义bean。它可以看做spring的配置文件,但并非真正的配置文件,我们需要通过编码java代码的方式创建bean。例如:
@Configuration
public class MyConfiguration {
@Bean
public Person person() {
return new Person();
}
}
在JavaConfig类上加@Configuration注解,相当于配置了标签。而在方法上加@Bean注解,相当于配置了标签。
此外,springboot还引入了一些列的@Conditional注解,用来控制bean的创建。
@Configuration
public class MyConfiguration {
@ConditionalOnClass(Country.class)
@Bean
public Person person() {
return new Person();
}
}
@ConditionalOnClass注解的功能是当项目中存在Country类时,才实例化Person类。换句话说就是,如果项目中不存在Country类,就不实例化Person类。
这个功能非常有用,相当于一个开关控制着Person类,只有满足一定条件才能实例化。
spring中使用比较多的Conditional还有:
- ConditionalOnBean
- ConditionalOnProperty
- ConditionalOnMissingClass
- ConditionalOnMissingBean
- ConditionalOnWebApplication
下面用一张图整体认识一下@Conditional家族:
4. Import注解
通过前面介绍的@Configuration和@Bean相结合的方式,我们可以通过代码定义bean。但这种方式有一定的局限性,它只能创建该类中定义的bean实例,不能创建其他类的bean实例,如果我们想创建其他类的bean实例该怎么办呢?
这时可以使用@Import注解导入。
4.1 普通类
spring4.2之后@Import注解可以实例化普通类的bean实例。例如:
先定义了Role类:
@Data
public class Role {
private Long id;
private String name;
}
接下来使用@Import注解导入Role类:
@Import(Role.class)
@Configuration
public class MyConfig {
}
然后在调用的地方通过@Autowired注解注入所需的bean。
@RequestMapping("/")
@RestController
public class TestController {
@Autowired
private Role role;
@GetMapping("/test")
public String test() {
System.out.println(role);
return "test";
}
}
聪明的你可能会发现,我没有在任何地方定义过Role的bean,但spring却能自动创建该类的bean实例,这是为什么呢?
这也许正是@Import注解的强大之处。
此时,有些朋友可能会问:@Import注解能定义单个类的bean,但如果有多个类需要定义bean该怎么办呢?
恭喜你,这是个好问题,因为@Import注解也支持。
@Import({Role.class, User.class})
@Configuration
public class MyConfig {
}
甚至,如果你想偷懒,不想写这种MyConfig类,springboot也欢迎。
@Import({Role.class, User.class})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
}
}
可以将@Import加到springboot的启动类上。
这样也能生效?
springboot的启动类一般都会加@SpringBootApplication注解,该注解上加了@SpringBootConfiguration注解。
而@SpringBootConfiguration注解,上面又加了@Configuration注解
所以,springboot启动类本身带有@Configuration注解的功能。
4.2 Configuration类
上面介绍了@Import注解导入普通类的方法,它同时也支持导入Configuration类。
先定义一个Configuration类:
@Configuration
public class MyConfig2 {
@Bean
public User user() {
return new User();
}
@Bean
public Role role() {
return new Role();
}
}
然后在另外一个Configuration类中引入前面的Configuration类:
@Import({MyConfig2.class})
@Configuration
public class MyConfig {
}
这种方式,如果MyConfig2类已经在spring指定的扫描目录或者子目录下,则MyConfig类会显得有点多余。因为MyConfig2类本身就是一个配置类,它里面就能定义bean。
但如果MyConfig2类不在指定的spring扫描目录或者子目录下,则通过MyConfig类的导入功能,也能把MyConfig2类识别成配置类。这就有点厉害了喔。
其实下面还有更高端的玩法。
swagger作为一个优秀的文档生成框架,在spring项目中越来越受欢迎。接下来,我们以swagger2为例,介绍一下它是如何导入相关类的。
众所周知,我们引入swagger相关jar包之后,只需要在springboot的启动类上加上@EnableSwagger2注解,就能开启swagger的功能。
其中@EnableSwagger2注解中导入了Swagger2DocumentationConfiguration类。
该类是一个Configuration类,它又导入了另外两个类:
- SpringfoxWebMvcConfiguration
- SwaggerCommonConfiguration
SpringfoxWebMvcConfiguration类又会导入新的Configuration类,并且通过@ComponentScan注解扫描了一些其他的路径。
SwaggerCommonConfiguration同样也通过@ComponentScan注解扫描了一些额外的路径。
4.3 ImportSelector
还有什么好说的,狂起点赞,简直完美。
上面提到的Configuration类,它的功能非常强大。但怎么说呢,它不太适合加复杂的判断条件,根据某些条件定义这些bean,根据另外的条件定义那些bean。
那么,这种需求该怎么实现呢?
这时就可以使用ImportSelector接口了。
首先定义一个类实现ImportSelector接口:
public class DataImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.sue.async.service.User", "com.sue.async.service.Role"};
}
}
重写selectImports方法,在该方法中指定需要定义bean的类名,注意要包含完整路径,而非相对路径。
然后在MyConfig类上@Import导入这个类即可:
@Import({DataImportSelector.class})
@Configuration
public class MyConfig {
}
朋友们是不是又发现了一个新大陆?
不过,这个注解还有更牛逼的用途。
@EnableAutoConfiguration注解中导入了AutoConfigurationImportSelector类,并且里面包含系统参数名称:spring.boot.enableautoconfiguration。
AutoConfigurationImportSelector类实现了ImportSelector接口。
除此之外,spring还提供了专门注册bean的接口:BeanDefinitionRegistryPostProcessor。
该方法会根据ENABLED_OVERRIDE_PROPERTY
的值来作为判断条件。
而这个值就是spring.boot.enableautoconfiguration。
换句话说,这里能根据系统参数控制bean是否需要被实例化,优秀。
我个人认为实现ImportSelector接口的好处主要有以下两点:
- 把某个功能的相关类,可以放到一起,方便管理和维护。
- 重写selectImports方法时,能够根据条件判断某些类是否需要被实例化,或者某个条件实例化这些bean,其他的条件实例化那些bean等。我们能够非常灵活的定制化bean的实例化。
4.4 ImportBeanDefinitionRegistrar
我们通过上面的这种方式,确实能够非常灵活的自定义bean。
但它的自定义能力,还是有限的,它没法自定义bean的名称和作用域等属性。
有需求,就有解决方案。
接下来,我们一起看看ImportBeanDefinitionRegistrar
接口的神奇之处。
先定义CustomImportSelector类实现ImportBeanDefinitionRegistrar接口:
public class CustomImportSelector implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);
registry.registerBeanDefinition("role", roleBeanDefinition);
RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
registry.registerBeanDefinition("user", userBeanDefinition);
}
}
重写registerBeanDefinitions
方法,在该方法中我们可以获取BeanDefinitionRegistry
对象,通过它去注册bean。不过在注册bean之前,我们先要创建BeanDefinition对象,它里面可以自定义bean的名称、作用域等很多参数。
然后在MyConfig类上导入上面的类:
@Import({CustomImportSelector.class})
@Configuration
public class MyConfig {
}
我们所熟悉的fegin功能,就是使用ImportBeanDefinitionRegistrar接口实现的:
5. PostProcessor
除此之外,spring还提供了专门注册bean的接口:BeanDefinitionRegistryPostProcessor
。
该接口的方法postProcessBeanDefinitionRegistry上有这样一段描述:
修改应用程序上下文的内部bean定义注册表标准初始化。所有常规bean定义都将被加载,但是还没有bean被实例化。这允许进一步添加在下一个后处理阶段开始之前定义bean。
如果用这个接口来定义bean,我们要做的事情就变得非常简单了。只需定义一个类实现BeanDefinitionRegistryPostProcessor接口。
@Component
public class MyRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);
registry.registerBeanDefinition("role", roleBeanDefinition);
RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
registry.registerBeanDefinition("user", userBeanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
重写postProcessBeanDefinitionRegistry
方法,在该方法中能够获取BeanDefinitionRegistry对象,它负责bean的注册工作。
不过细心的朋友可能会发现,里面还多了一个postProcessBeanFactory方法,没有做任何实现。
这个方法其实是它的父接口:BeanFactoryPostProcessor里的方法。
在应用程序上下文的标准bean工厂之后修改其内部bean工厂初始化。所有bean定义都已加载,但没有bean将被实例化。这允许重写或添加属性甚至可以初始化bean。
@Component
public class MyPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
DefaultListableBeanFactory registry = (DefaultListableBeanFactory)beanFactory;
RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);
registry.registerBeanDefinition("role", roleBeanDefinition);
RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
registry.registerBeanDefinition("user", userBeanDefinition);
}
}
既然这两个接口都能注册bean,那么他们有什么区别?
- BeanDefinitionRegistryPostProcessor 更侧重于bean的注册
- BeanFactoryPostProcessor 更侧重于对已经注册的bean的属性进行修改,虽然也可以注册bean。
此时,有些朋友可能会问:既然拿到BeanDefinitionRegistry对象就能注册bean,那通过BeanFactoryAware的方式是不是也能注册bean呢?
从下面这张图能够看出DefaultListableBeanFactory就实现了BeanDefinitionRegistry接口。
这样一来,我们如果能够获取DefaultListableBeanFactory对象的实例,然后调用它的注册方法,不就可以注册bean了?
说时迟那时快,定义一个类实现BeanFactoryAware接口:
@Component
public class BeanFactoryRegistry implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
DefaultListableBeanFactory registry = (DefaultListableBeanFactory) beanFactory;
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class);
registry.registerBeanDefinition("user", rootBeanDefinition);
RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
registry.registerBeanDefinition("user", userBeanDefinition);
}
}
重写setBeanFactory方法,在该方法中能够获取BeanFactory对象,它能够强制转换成DefaultListableBeanFactory对象,然后通过该对象的实例注册bean。
当你满怀喜悦的运行项目时,发现竟然报错了:
spring中bean的创建过程顺序大致如下:
BeanFactoryAware接口是在bean创建成功,并且完成依赖注入之后,在真正初始化之前才被调用的。在这个时候去注册bean意义不大,因为这个接口是给我们获取bean的,并不建议去注册bean,会引发很多问题。
【Spring Framework】12种spring中定义bean的方法的更多相关文章
- 面试官:spring中定义bean的方法有哪些?我一口气说出了12种,把面试官整懵了。
前言 在庞大的java体系中,spring有着举足轻重的地位,它给每位开发者带来了极大的便利和惊喜.我们都知道spring是创建和管理bean的工厂,它提供了多种定义bean的方式,能够满足我们日常工 ...
- Spring 梳理-profile与条件化定义bean
定义profile <beans> //root <beans profile="dev"> <bean id=.../> </beans ...
- Spring第12篇—— Spring对Hibernate的SessionFactory的集成功能
由于Spring和Hibernate处于不同的层次,Spring关心的是业务逻辑之间的组合关系,Spring提供了对他们的强大的管理能力, 而Hibernate完成了OR的映射,使开发人员不用再去关心 ...
- Spring Data JPA 在 @Query 中使用投影的方法
Spring Data JPA 在 @Query 中使用投影的方法 关于投影的基本使用可以参考这篇文章:https://www.baeldung.com/spring-data-jpa-project ...
- error LNK2005: “找到一个或多个多重定义的符号” 已经在 xxxx.obj 中定义 的解决方法
1 问题还原 这里我有三个源文件:Base.hpp, Base.cpp 和 main.cpp 在Base.hpp里面定义一个基类,注意,基类只包含构造函数和析构函数的声明,函数在Base.cpp里实现 ...
- Spring点滴六:Spring中定义bean的继承
在基于XML配置元数据中,bean标签可以包含很多配置信息,可以包含构造函数的参数,属性值以及其他一些初始化方法.子bean的定义可以继承父bean定义元数据,子bean定义可以根据需要重写父bean ...
- 【Spring】12、Spring Security 四种使用方式
spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: ...
- 一步一步深入spring(2)-三种方式来实例化bean
在一步一步深入spring(1)--搭建和测试spring的开发环境中提到了一种实例化bean的方式,也是最基本的使用构造器实例化bean 1.使用构造器实例化bean:这是最简单的方式,Spring ...
- Java 必须掌握的 12 种 Spring 常用注解!
1.声明bean的注解 @Component 组件,没有明确的角色 @Service 在业务逻辑层使用(service层) @Repository 在数据访问层使用(dao层) @Controller ...
随机推荐
- 准备 dubbo 学习目录
1. dubbo 背景及原理2. dubbo 架构分析4. dubbo 设计模式分析5. dubbo 实战使用6. dubbo 优化
- Chapter 1:Create You First 3D Scene With Three.js
1,各浏览器对WebGL的支持 手机浏览器对WebGL的支持: 书的源码:https://github.com/josdirksen/learning-threejs 第一次用浏览器打开代码可能无法正 ...
- Python 操作 Redis 发布订阅
Python 操作 Redis 发布订阅 介绍 Redis可以通过多个客户机订阅相同的频道,一个服务机在相应频道进行发布,从而实现在客户机收听服务机发布相应信息,可以利用这个机制实现多个客户机之间的信 ...
- 重写(Override)与重载(Overload)区别
重写是子类对父类的允许访问的方法的实现过程进行重新编写. 方法重写三要素: (1)方法名形参列表相同: (2)返回值类型和声明异常类型子类小于父类: (3)访问权限,子类大于等于父类. 重写的好处在于 ...
- [atARC126F]Affine Sort
记$g(k)$为$c$恰为$k$的合法三元组数,显然$f(k)=\sum_{i=1}^{k}g(i)$ 结论:若$\lim_{k\rightarrow \infty}\frac{g(k)}{k^{2} ...
- [luogu3706]硬币游戏
(可以参考洛谷4548,推导过程较为省略) 定义$g_{i}$表示随机$i$次后未出现给定字符串的概率,$f_{k,i}$表示随机$i$次后恰好出现$s_{k}$(指第$k$个字符串)的概率,设两者的 ...
- .NET Core基础篇之:配置文件读取
配置文件是每个项目最基础的部分,也是不可或缺的部分,比如:数据库连接.中间件属性等常见的配置. 今天这篇文章主要内容就是,在.Net Core项目中怎样去读取配置文件并使用. 提前准备 appsett ...
- 掌握Java的内存模型,你就是解决并发问题最靓的仔
摘要:如果编写的并发程序出现问题时,很难通过调试来解决相应的问题,此时,需要一行行的检查代码,这个时候,如果充分理解并掌握了Java的内存模型,你就能够很快分析并定位出问题所在. 本文分享自华为云社区 ...
- 洛谷 P4099 - [HEOI2013]SAO(树形 dp)
题面传送门 题意: 有一个有向图 \(G\),其基图是一棵树 求它拓扑序的个数 \(\bmod (10^9+7)\) \(n \in [1,1000]\) 如果你按照拓扑排序的方法来做,那恐怕你已经想 ...
- Codeforces 1089I - Interval-Free Permutations(析合树计数)
Codeforces 题面传送门 & 洛谷题面传送门 首先题目中涉及排列的 interval,因此可以想到析合树.由于本蒟蒻太菜了以至于没有听过这种神仙黑科技,因此简单介绍一下这种数据结构:我 ...