Spring 学习笔记(三):Spring Bean

1 Bean配置

Spring可以看做是一个管理Bean的工厂,开发者需要将Bean配置在XML或者Properties配置文件中。实际开发中常使用XML的格式,其中<bean>中的属性或子元素如下:

  • idBeanBeanFactory中的唯一标识,在代码中通过BeanFactory获取Bean的实例时候需要以此作为索引
  • classBean的具体实体类,使用包名+类名的形式指定
  • scope:指定Bean实例的作用域
  • <constructor-arg>:使用构造方法注入,指定构造方法的参数,index表示序号,ref指定对BeanFactory中其他Bean的引用关系,type指定参数类型,value指定参数常量值
  • <property>:用于设置一个属性,表示使用setter注入,name指定属性的名字,value指定要注入的值,ref指定注入的某个Beanid
  • <list>:用于封装List或者数组类型的依赖注入
  • <map>:封装Map类型的依赖注入
  • <set>:封装Set类型的依赖注入
  • <entry><map>的子元素,用于设置一个键值对

2 Bean实例化

Spring实例化Bean有三种方式:

  • 构造方法实例化
  • 静态工厂实例化
  • 实例工厂实例化

下面进行简单的演示。

2.1 构造方法实例化

Spring可以调用Bean对应的类的无参构造方法进行实例化,比如:

1public class TestBean { 2 public TestBean() 3 { 4 System.out.println("构造方法实例化"); 5 } 6}

配置文件如下:

<bean id="testBean" class="TestBean"/>

则会调用无参构造方法初始化。

其实就是只写一个<bean>就可以了,默认的话会调用无参构造方法初始化。

2.2 静态工厂实例化

静态工厂实例化需要在工厂类中配置一个静态方法来创建Bean,并添加factory-method元素,首先创建工厂类:

1public class TestBeanFactory { 2 private static final TestBean testBean = new TestBean(); 3 public static TestBean getInstance() 4 { 5 return testBean; 6 } 7}

接着配置文件通过class指定该工厂类,通过factory-method指定获取实例的方法:

<bean id="testBeanFactory" class="TestBeanFactory" factory-method="getInstance"/>

这样就可以通过id获取了:

TestBean test = (TestBean) context.getBean("testBeanFactory");

2.3 实例工厂实例化

实例工厂实例化与静态工厂实例化类似,不过是非静态方法,然后加上一个factory-bean元素,同样首先创建工厂类:

1public class TestBeanFactory { 2 public TestBean getInstance() 3 { 4 return new TestBean(); 5 } 6}

在配置文件需要添加两个Bean,一个指定工厂类,一个指定使用哪一个工厂类以及使用工厂类的哪一个方法:

1<bean id="factory" class="TestBeanFactory" /> <!--指定工厂类--> 2<bean id="testBeanFactory" factory-bean="factory" factory-method="getInstance" /> <!--指定工厂Bean以及哪一个工厂方法-->

获取:

TestBean test = (TestBean) context.getBean("testBeanFactory");

3 Bean作用域

3.1 分类

<bean>中的scope可以指定的作用域如下:

  • singleton:默认作用域,在Spring容器只有一个Bean实例
  • prototype:每次获取Bean都会返回一个新的实例
  • request:在一次HTTP请求中只返回一个Bean实例,不同HTTP请求返回不同的Bean实例,仅在Spring Web应用程序上下文使用
  • session:在一个HTTP Session中,容器将返回同一个Bean实例,仅在Spring Web应用程序上下文中使用
  • application:为每个ServletContext对象创建一个实例,即同一个应用共享一个Bean实例,仅在Spring Web应用程序上下文使用
  • websocket:为每个WebSocket对象创建一个Bean实例,仅在Spring Web应用程序上下文使用

下面具体说一下最常用的两个:singletonprototype

3.2 singleton

scope设置为singleton时,Spring IoC仅生成和管理一个Bean实例,使用id/name获取Bean实例时,IoC容器返回共享的Bean实例。设置方式如下:

1<bean id="testBean" class="TestBean"/> 2<bean id="testBean" class="TestBean" scope="singleton"/>

因为这是默认的作用域,设置的话IDE也智能提示是多余的:

在这里插入图片描述

所以通过不需要加上scope,测试例子:

1TestBean test1 = (TestBean) context.getBean("testBean"); 2TestBean test2 = (TestBean) context.getBean("testBean"); 3System.out.println(test1 == test2);

输入的结果为True

3.3 prototype

每次获取Bean时都会创建一个新的实例,例子如下:

1<bean id="testBean" class="TestBean" scope="prototype"/> 2 3 4TestBean test1 = (TestBean) context.getBean("testBean"); 5TestBean test2 = (TestBean) context.getBean("testBean"); 6System.out.println(test1 == test2);

测试结果为False

4 Bean生命周期

Spring可以管理作用域为singleton的生命周期,在此作用域下Spring能精确知道Bean何时被创建,何时初始化完成以及何时被摧毁。Bean的整个生命周期如下:

  • 实例化Bean
  • 进行依赖注入
  • 如果Bean实现了BeanNameAware,调用setBeanName
  • 如果Bean实现了BeanFactoryAware,调用setBeanFactory
  • 如果Bean实现了ApplicationContextAware,调用setApplicationContext
  • 如果Bean实现了BeanPostProcessor,调用postProcessBeforeInitialization
  • 如果Bean实现了InitializingBean,调用afterPropertiesSet
  • 如果配置文件配置了init-method属性,调用该方法
  • 如果实现了BeanPostProcessor,调用postProcessAfterInitialization,注意接口与上面的相同但是方法不一样
  • 不需要时进入销毁阶段
  • 如果Bean实现了DisposableBean,调用destroy
  • 如果配置文件配置了destroy-method,调用该方法

下面用代码进行演示:

1public class TestBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, BeanPostProcessor, InitializingBean, DisposableBean { 2 public TestBean() 3 { 4 System.out.println("调用构造方法"); 5 } 6 7 @Override 8 public void setBeanName(String s) { 9 System.out.println("调用BeanNameAware的setBeanName"); 10 } 11 12 @Override 13 public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 14 System.out.println("调用BeanFactoryAware的setBeanFactory"); 15 } 16 17 @Override 18 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 19 System.out.println("调用ApplicationContextAware的setApplicationContext"); 20 } 21 22 @Override 23 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 24 System.out.println("调用BeanPostProcessor的postProcessBeforeInitialization"); 25 return null; 26 } 27 28 @Override 29 public void afterPropertiesSet() throws Exception { 30 System.out.println("调用InitializingBean的afterPropertiesSet"); 31 } 32 33 public void initMethod() 34 { 35 System.out.println("调用XML配置的init-method"); 36 } 37 38 @Override 39 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 40 System.out.println("调用BeanPostProcessor的postProcessAfterInitialization"); 41 return null; 42 } 43 44 @Override 45 public void destroy() throws Exception { 46 System.out.println("调用DisposableBean的destroy"); 47 } 48 49 public void destroyMethod() 50 { 51 System.out.println("调用XML配置的destroy-method"); 52 } 53}

配置文件如下,指定了init-method以及destroy-method

<bean id="testBean" class="TestBean" init-method="initMethod" destroy-method="destroyMethod"/>

测试:

1public static void main(String[] args) { 2 ConfigurableApplicationContext context = new FileSystemXmlApplicationContext("classpath:applicationContext.xml"); 3 TestBean test = (TestBean) context.getBean("testBean"); 4 ((BeanDefinitionRegistry) context.getBeanFactory()).removeBeanDefinition("testBean"); 5}

输出如下:

在这里插入图片描述

如果没有最后一行的手动删除Bean定义是不会看见最后两行的输出的,另外,这里没有调用BeanPostProcessor接口的两个方法,如果把scope改为prototype,输出如下:

在这里插入图片描述

可以看到首先对Bean进行一次初始化,并且再次生成一个新的实例,而且调用了BeanPostProcessor的两个方法。但是需要注意Spring不会管理scopeprototype的销毁,所以图中没有看到调用销毁的方法。

5 Bean装配方式

Spring支持以下两种装配方式:

  • 基于XML装配
  • 基于注解装配
  • 显式Bean装配

Bean的装配方式也就是Bean的依赖注入方式,下面分别进行阐述。

5.1 基于XML装配

基于XML装配也就是在XML文件中指定使用构造方法注入或者setter注入,比如:

1public class TestBean { 2 private final List<String> stringList; 3 private String s; 4 5 public TestBean(List<String> stringList) { 6 this.stringList = stringList; 7 } 8 9 public void setS(String s) 10 { 11 this.s = s; 12 } 13 14 @Override 15 public String toString() { 16 return stringList.toString() + "\n" + s + "\n"; 17 } 18}

Bean有一个带参数的构造方法以及一个setter,接着在XML中指定相应的值即可:

1<bean id="testBean" class="TestBean"> 2 <constructor-arg index="0"> 3 <list> 4 <value>1</value> 5 <value>2</value> 6 </list> 7 </constructor-arg> 8 <property name="s" value="444" /> 9</bean>

测试:

1ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 2System.out.println(context.getBean("testBean"));

5.2 基于注解装配

尽管XML方式可以简单地装配Bean,但是一旦Bean过多就会造成XML文件过于庞大,不方便以后的升级和维护,因此推荐使用基于注解的装配方式,先来看一下常用的注解:

  • @Autowired:自动装配,默认按照Bean的类型进行装配,这是Spring的注解
  • @Resource:与@Autowired类似,但是是按名称进行装配,当找不到与名称匹配的Bean时才按照类型进行装配,这是JDK的注解
  • @Qualifier:与@Autowired配合使用,因为@Autowired默认按Bean类型进行装配,使用@Qualifier可以按名称进行装配
  • @Bean:方法上的注解,用于产生一个Bean,然后交由Spring管理
  • @Component:表示一个组件对象,加上了该注解就能实现自动装配,默认的Beanid为使用小驼峰命名法的类
  • @Repository/@Service/@Controller:实际上是@Component的别名,只不过是专门用于持久层/业务层/控制层的,从源码可以看出三个注解的定义除了名字不一样其他都一致,并且都是@Component的别名:

在这里插入图片描述

官方文档也提到相比起使用@Component,使用@Repository/@Service/@Controller在持久层/业务层/控制层更加合适,而不是统一使用@Component

在这里插入图片描述

5.3 注解使用示例

5.3.1 @Bean

@Bean示例如下:

1public class TestBean implements BeanNameAware{ 2 @Override 3 public void setBeanName(String s) { 4 System.out.println("setBeanName"); 5 } 6} 7 8@Configuration 9public class Config { 10 @Bean 11 public TestBean getBean() 12 { 13 return new TestBean(); 14 } 15} 16 17public class Main { 18 public static void main(String[] args) { 19 ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); 20 context.getBean("getBean"); 21 } 22}

注意通过@Bean自动产生的Beanid为方法名,而不是Bean的类名的小驼峰形式。

5.3.2 其他

@Autowired/@Resource/@Qualifier/@Repository/@Service/@Controller综合示例,首先创建如下包以及文件:

在这里插入图片描述

1@Controller 2public class TestController { 3 @Resource 4 private TestService service; 5 6 public void save() 7 { 8 System.out.println("controller save"); 9 service.save(); 10 } 11} 12 13 14@Service 15public class TestService { 16 @Autowired 17 @Qualifier("testRepository1") 18 private TestInterface repository1; 19 20 @Autowired 21 @Qualifier("testRepository2") 22 private TestInterface repository2; 23 public void save() 24 { 25 System.out.println("service save"); 26 repository1.save(); 27 repository2.save(); 28 } 29} 30 31 32@Repository 33public class TestRepository1 implements TestInterface{ 34 @Override 35 public void save() { 36 System.out.println("repository1 save"); 37 } 38} 39 40 41@Repository 42public class TestRepository2 implements TestInterface{ 43 @Override 44 public void save() { 45 System.out.println("repository2 save"); 46 } 47} 48 49 50public interface TestInterface { 51 void save(); 52} 53 54 55public class Main { 56 public static void main(String[] args) { 57 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 58 ((TestController)context.getBean("testController")).save(); 59 } 60}

配置文件:

<context:component-scan base-package="bean" />

TestService中,使用了@Qualifier

1@Autowired 2@Qualifier("testRepository1") 3private TestInterface repository1; 4 5@Autowired 6@Qualifier("testRepository2") 7private TestInterface repository2;

因为TestInterface有两个实现类,@Autowired不知道是选择TestRepository1还是TestRepository2,因此需要加上@Qualifier,指定需要注入的Beanid,或者使用@Resouce

1@Resource 2private TestInterface testRepository1; 3 4@Resource 5private TestInterface testRepository2;

但是要注意这样默认了成员的名字就是Beanid,可以看到这里的名字是testRepository1testRepository2而不是repository1repository2

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )