【Spring基础之IOC】Spring IOC创建bean的几种方式
这里写目录标题
- 【Spring基础之IOC】Spring IOC创建bean的几种方式
- 一. 简介:
- 二.注册/创建bean的几种方式。
- (一).基于纯xml创建bean的方式。
- (二).基于xml的componentScan加上注解的方式
- (三).基于javaConfig(@Configuration)+ @Bean的方式注册bean
- (四). 通过实现FactoryBean 来注册bean。
- (五).通过JavaConfig的(@Configuration) + @ComponentScan + 注解的方式注册bean
- (六).通过@Configuration + @Import注解的方式创建bean
- (七).通过@Configuration配置类中的@Import + ImportSelector方式注册多个bean实例。
- (八). 通过@Configuration + @Import + ImportBeanDefinitionRegistrar来注册bean
- (九). 通过@ComponentScan + BeanDefinitionRegistryPostProcessor来注册bean
- (十). 通过@ComponentScan + BeanFactoryPostProcessor来完成bean的注册。
一. 简介:
Spring是一个轻量开源级别的框架。
Spring的出现是为了解决企业级业务逻辑层面和其它层面之间的耦合。
Spring是一个基于IOC和AOP的框架,这两者是spring中最核心的功能。
AOP: 面向切面编程,对代码层面进行增强,如spring事务
IOC: 控制反转,避免过于复杂的对象创建,基于依赖倒置原则引入的一种设计思想。
DI: 依赖注入,IOC的具体实现。
容器: 负责集中化管理bean。
二.注册/创建bean的几种方式。
准备工作:
1.创建新的maven项目:
2.引入相关的maven依赖
<!-- 引入spring相关的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- junit测试相关的依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
由于spring-context内部已经依赖了spring-beans,spring-core, spring-expression, spring-jcl 等相关jar包,所以只需要暂时引入spring-context包即可。
(一).基于纯xml创建bean的方式。
使用 XML 配置,可以让 bean 定义分布在多个 XML 文件上,这种方法直观优雅清晰明显. 通常,每个单独的 XML 配置文件代表架构中的一个逻辑层或模块. 这种配置方式在spring2.5之前非常常用。
实体类与测试类:
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
@org.junit.Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-ioc.xml");
Student bean = context.getBean(Student.class);
System.out.println(bean);
}
}
xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="spring.ioc.Student" id="student"></bean>
</beans>
输出结果:
(二).基于xml的componentScan加上注解的方式
Spring 2.5 支持基于注解的元数据配置,通过配置componentScan来扫描标注了@Controller @Service @Repository @Component的类,将这些类注册成bean组件。
@Controller
//@Service
//@Repository
//@Component
public class StudentController {
@Value("李雷")
private String name;
public void say(){
System.out.println("My name is " + name);
}
}
public class Test {
@org.junit.Test
public void test02(){
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-ioc.xml");
StudentController bean = context.getBean(StudentController.class);
bean.say();
}
}
xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="spring.ioc"></context:component-scan>
</beans>
测试:
(三).基于javaConfig(@Configuration)+ @Bean的方式注册bean
从 Spring 3.0 开始, 由 Spring JavaConfig 项目提供的功能已经成为 Spring 核心框架的一部分. 因此,你可以使用 Java 配置来代替 XML 配置定义外部 bean . 要使用这些新功能,请参阅 @Configuration, @Bean, @Import, 和 @DependsOn 注解.
Spring 的 Bean (至少一个) 由容器来管理,基于 XML 的元数据配置将这些 bean 配置为 元素.并放置于 元素内部. 基于 Java 的配置通常是使用 @Configuration 注解过的类中,在它的方法上使用 @Bean 注解.
这些 bean 定义会对应到构成应用程序的实际对象. 通常你会定义服务层对象,数据访问对象(DAOs) ,表示对象(如Struts Action 的实例),基础对象(如 Hibernate 的 SessionFactories, JMS Queues,) . 通常,不会在容器中配置细粒度的 domain 对象,因为创建和加载 domain 对象通常是 DAO 和业务逻辑的职责. 但是,你可以使用 Spring 与 AspectJ 集成独立于 IoC 容器来创建的对象,请参阅 Spring 使用 AspectJ 进行依赖注入 domain 对象.
通常在项目中使用到第三方的bean时,常常会使用这种方式,这种方式可以人工干预创建bean的过程,给开发带来了一定的便利性。
@Configuration
public class JavaConfigApp {
@Bean
public Student student(){
return new Student();
}
}
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试:
public class Test {
@org.junit.Test
public void test03(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
Student bean = context.getBean(Student.class);
System.out.println(bean);
}
}
结果:
(四). 通过实现FactoryBean 来注册bean。
FactoryBean区别于BeanFacotory,它也是一个bean,通过getObject方法来手动创建真实的bean。实现BeanFactory接口需要实现三个方法。
getObject(): 自定义创建bean的方法。
getObjectType(): 注册返回bean的类型。
getSingleton(): 是否为单例bean。
public class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
@Component
public class TeacherBeanFactory implements FactoryBean<Teacher> {
public Teacher getObject() throws Exception {
Teacher t = new Teacher();
t.setName("小明");
return t;
}
public Class<?> getObjectType() {
return Teacher.class;
}
public boolean isSingleton() {
return true;
}
}
@Configuration
@ComponentScan(basePackages = "spring.ioc")
public class JavaConfigApp {
}
测试:
@org.junit.Test
public void test05(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
Teacher bean = (Teacher) context.getBean("teacherBeanFactory");
TeacherBeanFactory bean1 = (TeacherBeanFactory) context.getBean("&teacherBeanFactory");
System.out.println(bean);
System.out.println(bean1);
}
结果:
重点注意: 如果根据注解标注类名首字母小写来获取bean,context.getBean(“teacherBeanFactory”),这样获取到的是getObject创建的bean,如果需要获取原型bean,那么需要通过&teacherBeanFactory
方式,context.getBean(“&teacherBeanFactory”)。
(五).通过JavaConfig的(@Configuration) + @ComponentScan + 注解的方式注册bean
@Configuration
@ComponentScan(basePackages = "spring.ioc")
public class JavaConfigApp {
}
@Controller
//@Service
//@Repository
//@Component
public class StudentController {
@Value("李雷")
private String name;
public void say(){
System.out.println("My name is " + name);
}
}
测试:
@org.junit.Test
public void test04(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
StudentController bean = context.getBean(StudentController.class);
System.out.println(bean);
}
结果:
(六).通过@Configuration + @Import注解的方式创建bean
可以在@Configuration配置类中使用 @Import 注解直接导入需要注册成bean的类。这种方式不能人工干预bean的创建, 并且这种方式注册的bean名字并不是类名首字母小写,所以获取bean的时候需要使用类的全限定名或者根据类型获取 , 在需要注册成bean的类上不需要标注任何注解。
@Configuration
@Import(Teacher.class)
public class JavaConfigApp {
}
public class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
测试:
public class Test {
//基于@Import + 需要注册的组件
@org.junit.Test
public void test06(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
Teacher bean = context.getBean(Teacher.class);
System.out.println(bean);
bean = (Teacher) context.getBean("spring.ioc.Teacher");
System.out.println(bean);
}
}
结果:
(七).通过@Configuration配置类中的@Import + ImportSelector方式注册多个bean实例。
这种方式比较起上一种方式来说更加方便,可以同时注册多个bean的实例,同样的不需要在@Import引入的组件类中加任何注解,同时获取bean的时候,需要根据类的全限定名或者类型获取,不能直接使用类的首字母小写进行获取。
@Configuration
@Import(TeacherImportSelector.class)
public class JavaConfigApp {
}
public class TeacherImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"spring.ioc.Teacher", Student.class.getName()};
}
}
测试:
//基于@Import + ImportSelector注册多个bean实例
@org.junit.Test
public void test07(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
//根据类型获取
Teacher teacher = context.getBean(Teacher.class);
System.out.println(teacher);
Student student = context.getBean(Student.class);
System.out.println(student);
Teacher teacher1 = (Teacher) context.getBean("spring.ioc.Teacher");
System.out.println(teacher1);
Student student1 = (Student) context.getBean("spring.ioc.Student");
System.out.println(student1);
Teacher teacher2 = context.getBean("teacher", Teacher.class);
System.out.println(teacher2);
Student student2 = context.getBean("student", Student.class);
System.out.println(student2);
}
结果:
根据类名首字母小写是无法获取bean的。
(八). 通过@Configuration + @Import + ImportBeanDefinitionRegistrar来注册bean
这种方式需要自己实现ImportBeanDefinitionRegistrar类,并复写registerBeanDefinitions()方法,相比前两种更加的灵活,可以在生产bean之前对bean的一些属性做一系列的修改,比如作用域,bean的名字,自动注入的模式等 , 最后通过registry.registerBeanDefinition()将bean定义放到beanDefinitionMap中统一管理。
@Configuration
@Import({TeacherBeanDefinetionRegistry.class})
public class JavaConfigApp {
}
public class TeacherBeanDefinetionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
String name = Teacher.class.getName();
beanDefinition.setBeanClass(Teacher.class);
beanDefinition.setBeanClassName(name);
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
registry.registerBeanDefinition(name, beanDefinition);
RootBeanDefinition bd = new RootBeanDefinition(Student.class);
bd.setScope(BeanDefinition.SCOPE_SINGLETON);
registry.registerBeanDefinition("student", bd);
}
}
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
public class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
测试:
//基于@Import + ImportBeanDefinitionRegistrar来手动注册
@org.junit.Test
public void test08(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
//根据名字获取
Teacher teacher = context.getBean(Teacher.class.getName(), Teacher.class);
System.out.println(teacher);
Student student = context.getBean("student" ,Student.class);
System.out.println(student);
//根据类型获取
Teacher teacher1 = context.getBean(Teacher.class);
System.out.println(teacher1);
Student student1 = context.getBean(Student.class);
System.out.println(teacher1);
}
结果:
(九). 通过@ComponentScan + BeanDefinitionRegistryPostProcessor来注册bean
实现了该结构就有注册和获取Bean定义的能力,初始化ioc容器的过程中,会多次调用实现了BeanDefinitionRegistryPostProcessor
接口的postProcessBeanDefinitionRegistry()方法注册到容器之中。
所以在这里只需要将BeanDefinitionRegistryPostProcessor想办法注
册成bean即可。
1.可以使用@Import 直接将其注册成bean
2.可以使用@ComponentScan + 注解的方式将其注册成bean
@Configuration
@ComponentScan(basePackages = "spring.ioc")
//@Import(TeacherBeanDefinitionRegistryPostProcessor.class)
public class JavaConfigApp {
}
public class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
@Component
public class TeacherBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
beanDefinitionRegistry.registerBeanDefinition("teacher", new RootBeanDefinition(Teacher.class));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
测试:
@org.junit.Test
public void test09(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
System.out.println(context.getBean("teacher", Teacher.class));
}
结果:
(十). 通过@ComponentScan + BeanFactoryPostProcessor来完成bean的注册。
通过结构类型关系图可以看出,BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor有注册bean的功能,然而BeanFactoryPostProcessor拥有修改bean的功能。
1、 BeanDefinitionRegistryPostProcessor:侧重于bean的注册。
2、 BeanFactoryPostProcessor:侧重于对已经注册的bean的属性进行修改,但是可以通过将方法内部的ConfigurableListableBeanFactory向下转型,实现BeanDefinitionRegistry有注册功能的DefaultListableBeanFactory
@Component
public class TeacherBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) configurableListableBeanFactory;
factory.registerBeanDefinition("teacher", new RootBeanDefinition(Teacher.class));
}
}
@Configuration
@ComponentScan(basePackages = "spring.ioc")
public class JavaConfigApp {
}
public class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
测试:
public class Test {
@org.junit.Test
public void test09(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigApp.class);
System.out.println(context.getBean("teacher", Teacher.class));
}
}
结果:
总的来说,创建bean的方式有很多种,对于实际应用往往使用比较多的就是javaConfig方式的@Import导入组件,其它的方式并不常用,可以在适当的时候择优选择。