目录
Spring Bean Scopes - Bean 的作用域
基本概念
Java Bean
Java Bean是一个实现了通用规范的类,该规范主要约束有:
1. 这个类需要是public 的, 然后需要有个无参数的构造函数
对外可以访问Bean,所有必须是public,别的类要在内部使用Bean,但又不知道你有几个构造器,所以必须有个无参构造器,这样通过反射直接就能获得对象。
2. 这个类的属性应该是private 的, 通过setXXX()和getXXX()来访问(实现属性的getter与setter)
假若别人想用动态设置你的Bean的属性,你大可以将所有属性设置为public,但是这样显得很不规范,所以设置getter/setter规范化属性的获取与设置。
3. 这个类应该是可以序列化的, 即可以把bean的状态保存的硬盘上, 以便以后来恢复。
将设计好的Bean保存下来,下一次使用是直接读取信息即可。
IoC-控制翻反转
IoC 不是一种技术,只是一种思想。传统应用程序都是由我们在类内部主动创建(new)依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,容器通过注解或者配置文件读取信息,整合对象,通过容器获取对象。
DI—依赖注入
IoC 和 DI 是同一个概念的不同角度描述,实际上是相同的思想。
依赖注入指组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
所谓控制反转,依赖注入,无非就是当前的Bean需要依赖另一个Bean,可以直接new一个,如果Bean比较少,new几个也没关系,如果多了,那关系就很复杂,很难查看Bean之间的依赖关系,要简化这种关系,索性就将Bean都交给通用容器处理,依赖关系只需要在外部写清楚即可,,即配置属性,或直接使用注解。要用到Bean则直接通过容器获取。
IoC 容器
IoC 容器就是具有依赖注入功能的容器,IoC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中 new 相关的对象,应用程序由 IoC 容器进行组装。在 Spring 中 BeanFactory 是 IoC 容器的实际代表者。
通过配置文件,Spring IoC 容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。一般使用基于 xml 配置文件进行配置元数据,而且 Spring 与配置文件完全解耦的,可以使用其他任何可能的方式进行配置元数据,比如注解、基于 java 文件的、基于属性文件的配置都可以。
Spring Ioc 容器的代表就是 org.springframework.beans 包中的 BeanFactory 接口, BeanFactory 接口提供了 IoC 容器最基本功能;而 org.springframework.context 包下的 ApplicationContext 接口扩展了 BeanFactory ,还提供了与Spring AOP 集成、国际化处理、事件传播及提供不同层次的 context 实现 (如针对 web 应用的 WebApplicationContext )。简单说, BeanFactory 提供了 IoC 容器最基本功能,而 ApplicationContext 则增加了更多支持企业级功能支持。
具体的:
-
XmlBeanFactory : BeanFactory 实现,提供基本的 IoC 容器功能,可以从 classpath或文件系统等获取资源;
File file = new File("fileSystemConfig.xml");
Resource resource = new FileSystemResource(file);
BeanFactory beanFactory = new XmlBeanFactory(resource);
- ClassPathXmlApplicationContext : ApplicationContext 实现,从 classpath 获取配置文件;
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath.xml");
- FileSystemXmlApplicationContext : ApplicationContext 实现 ,从文件系统获取配置文件。
BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.xml");
即类的实例不通过new 来获得,而通过IoC容器获取相应的配置信息,调用容器相应方法获得。
spring既然是个框架,所谓框架,即别人写好的一整套代码,咱们直接拿过来用,在此框架上遵循该框架的规则并利用框架规则进行方便快捷的开发,要先下载并导入spring进行开发,如果使用IDEA可自动导入.
如果使用IDEA创建Maven工程,则要在pom.xml下配置依赖,使用alt+enter进行自动导入即可(注意更换版本号)。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.1.1.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
可以看到,在依赖中导入了spring-core与spring-context
在目录src/main/java下是整个项目的java代码,划分成多个package,每个package下多个class
在目录src/main/resources是整个项目的xml配置文件.
Spring 中bean 的定义、配置与使用
1.创建Bean
package bean;
public class Person {
public Person(){}
private String name;
private int age;
public void setName(String name){
this.name=name;
}
public void setAge(int age){
this.age=age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void print(){
System.out.println(name+" "+age);
}
}
2.基于 XML 配置Bean
通过配置XML来获取该类的实例,有三种写法:一般方法、缩写方法、p schema
配置不包含其它bean的单个bean
<?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">
<!--使用p schema写法时要加入xmlns:p="http://www.springframework.org/schema/p">
<!-一般配置方法-->
<bean id="personOne" class="bean.Person">
<property name="name">
<value>"小明"</value>
</property>
<property name="age">
<value>15</value>
</property>
</bean>
<!--缩写配置方法-->
<bean id="personTwo" class="bean.Person">
<property name="name" value="贵师大生" />
<property name="age" value="21" />
</bean>
<!--p schema 需要时自查-->
</beans>
每个bean都是一个实例,bean有两个标签,id表示该实例的id标识,通过ApplicationContext对象的getBean()方法传入id即可获得对应类的实例,class表示该对象的Class。每个bean可以设置property,property有三个标签,name="xxx"就是类中定义的变量名,value="xxx"就是name对应的变量的值,ref="xxx‘’就是变量对应的类中对对象的引用,用于某个类中包含其它类,该bean中的ref即引用具体的bean,详见下面的嵌套bean。
配置包含有依赖关系的bean,即bean中包含其它bean
package bean;
public class Customer {
private Person person;
private int id;
public Customer(Person person){
this.person=person;
}
public Customer(){}
public void setPerson(Person person){
this.person=person;
}
public Person getPerson(){
return person;
}
public void setId(int id){
this.id=id;
}
public int getId(){
return id;
}
public void print(){
System.out.println(person.getName()+" "+person.getAge()+" "+id);
}
}
Customer内部有一个Person对象,肯定无法通过配置bean中的value进行配置,有三种办法
1.通过ref
<?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 id="personOne" class="bean.Person">
<property name="name">
<value>"小明"</value>
</property>
<property name="age">
<value>15</value>
</property>
</bean>
<!--Customer中有一个Person变量,通过ref引用具体的Person bean-->
<bean id="customerOne" class="bean.Customer">
<property name="person" ref="personOne" />
<property name="id" value="2017243202" />
</bean>
</beans>
使用ref则ref引用的对象不会被其它bean所引用,否则会报错
2.在bean中配置bean
在bean中的property,name为变量名,后面跟着一个bean的定义即可
<!--内部嵌套bean-->
<bean id="customerTwo" class="bean.Customer">
<property name="person" >
<bean class="bean.Person">
<property name="name" value="龙马夹袋" />
<property name="age" value="33" />
</bean>
</property>
<property name="id" value="88" />
</bean>
3.通过构造函数注入
<bean id="customerThree" class="bean.Customer">
<constructor-arg>
<bean class="bean.Person">
<property name="name" value="坂田达" />
<property name="age" value="35" />
</bean>
</constructor-arg>
<property name="id" value="1234567" />
</bean>
如果通过构造函数注入,就要有相应的构造函数,构造函数的参数应该是对应的类的对象,同时,如果有构造函数,一定要有默认的构造函数,否则会报错。
bean的自动装配
Spring 支持 5 种自动装配模式,如下:
no —— 默认情况下,不自动装配,通过 ref attribute 手动设定。
byName —— 根据 Property 的 Name 自动装配,如果一个 bean 的 name ,和另一个 bean 中的 Property 的 name 相同,则自动装配这个 bean 到 Property 中。
byType —— 根据 Property 的数据类型( Type )自动装配,如果一个 bean 的数据类型,兼容另一个 bean 中 Property 的数据类型,则自动装配。
constructor —— 根据构造函数参数的数据类型,进行 byType 模式的自动装配。
autodetect —— 如果发现默认的构造函数,用 constructor 模式,否则,用 byType 模式。
1.默认引用装配
默认情况下,需要通过 ref 来装配 bean 。
2.通过属性名(实际上就是变量名,xml中bean的id)装配
根据属性 Property 的名字装配 bean ,这种情况,CustomerService 设置了 autowire="byName" ,Spring 会自动寻找与属性名字 customerDAO (实际上就是变量名)相同的 bean ,找到后,通过调用 setCustomerDAO(CustomerDAO customerDAO) 将其注入属性。
如果根据 Property name 找不到对应的 bean 配置就会装配失败,运行后,CustomerService 中 customerDAO=null 。
3.通过类型(实际上就是类的类型,xml中的class)装配
根据属性 Property 的数据类型自动装配,这种情况,CustomerService 设置了 autowire="byType" ,Spring 会自动寻找与属性类型相同的 bean ,找到后,通过调用 setCustomerDAO(CustomerDAO customerDAO) 将其注入。
如果配置文件中有两个类型相同的 bean,将抛出 UnsatisfiedDependencyException 异常,见以下: Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 所以,一旦选择了 byType 类型的自动装配,请确认你的配置文件中每个数据类型定义一个唯一的 bean 。
4.构造器注入
这种情况下,Spring 会寻找与参数数据类型相同的 bean ,通过构造函数 public Customer(Person person) 将其注入。
在主应用程序中获取bean
import bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
//ApplicationContext实现ClassPathXmlApplicationContext接口,读取配置文件,成为beans工厂
private static ApplicationContext context;
public static void main(String[] args){
context=new ClassPathXmlApplicationContext("Beans.xml");
Person person=(Person)context.getBean("personTwo");
person.print();
}
}
可以看到,context是从类路径中获取配置的IoC容器,调用getBean方法获取bean,完成控制反转。
在xml文件下可手动配置bean,beans下的bean就代表具体实例,属性property中的id表示所引用的class的id标识,class代表类路径,默认前缀为src/main/java,name表示类的变量名(实例字段),value表示该变量的值,以此完成bean的初始化赋值。
在需要调用时,先读取配置文件,返回一个ApplicationContext对象,通过该对象获取一个实例,通过具体配置bean获得的实例在通过id返回对象时返回的是同一个对象,两者引用同一块内存。
3.基于注解配置Bean
可以想到,通过xml完成信息的注入,确实解耦合,但是也挺麻烦,注解是为 Spring 容器提供 Bean 定义的信息,把 XML 定义的信息通过类注解描述出来。Spring容器三大要素:Bean 定义、 Bean 实现类以及 Spring 框架。如果采用 XML 配置,Bean 定义和 Bean 实现类本身分离,而采用注解配置,Bean 定义在 Bean 实现类上注解就可以实现。
正常手动配置一个 bean :
1.写java 类
2.写配置文件xml,配置bean
3.在主类中解析xml文件获取Ioc容器,通过容器获取bean
通过注解,可更轻松描述上述事情
@Component
被此注解标注的类将被 Spring 容器自动识别,自动生成 Bean 定义。
package bean;
import org.springframework.stereotype.Component;
//该注解表示自动装配到Ioc容器
@Component
public class Lj {
private String info;
public void setInfo(String info){
this.info=info;
}
public String getInfo(){
return info;
}
}
除此之外,Spring 有三个与 @Component 等效的注解:
- @Controller:对应表现层的 Bean,也就是 Action 。
- @Service:对应的是业务层 Bean 。
- @Repository:对应数据访问层 Bean 。
@Autowired
自动注入信息(形成xml中的bean配置),即配置依赖关系,可以写在字段上,或者方法上。@Autowired 默认按类型装配,默认情况下要求依赖对象必须存在,如果要允许 null 值,可以设置它的 required 属性为 false。
package bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Lsl {
private String info;
//该注解表示该字段信息自动扫描并装入Ioc容器,表示Lsl依赖于Lj
@Autowired
private Lj lj;
public void setInfo(String info) {
this.info = info;
}
public void setLj(Lj lj){
this.lj=lj;
}
public Lj getLj(){
return lj;
}
public void print(){
System.out.println(info+lj.getInfo());
}
}
@Qualifier 注解
这个注解通常和@Autowired 一起使用,用于解决Bean的歧义性问题。
有一个接口Interface Hello
有两个类Hello1,Hello2分别实现了该接口,配置文件中的id分别为hello1,hello2
另一个类声明了Hello hello,使用@Autowired 自动注入,但不知道使用Hello1还是Hello2,于是使用@Qualifier进行标明
具体来看一下:
有接口wf
package bean;
public interface wf {
void setInfo(String info);
String getInfo();
}
有两个class实现了wf
package bean;
import org.springframework.stereotype.Component;
//该注解表示自动装配到Ioc容器
@Component
public class Lf implements wf{
private String info;
public void setInfo(String info){
this.info=info;
}
public String getInfo(){
return getInfo();
}
}
package bean;
import org.springframework.stereotype.Component;
//该注解表示自动装配到Ioc容器
@Component
public class Lj implements wf{
private String info;
public void setInfo(String info){
this.info=info;
}
public String getInfo(){
return info;
}
}
而在Lsl下有:
package bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class Lsl {
private String info;
//该注解表示将字段信息自动扫描并装入Ioc容器
@Autowired
//lj和lf都实现了wf接口,那么,该wf到底指的是哪一个具体实现呢,使用@Qualifer
@Qualifier("lj")
private wf lj;
public void setInfo(String info) {
this.info = info;
}
public wf getWf(){
return lj;
}
public void print(){
System.out.println(info+lj.getInfo());
}
}
具体实现:
import bean.Lsl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class GetBeanByAnnotation {
private static ApplicationContext context;
public static void main(String[] args){
context=new ClassPathXmlApplicationContext("Annotation.xml");
//获取bean后,可通过setter方法注入你想要的
Lsl lsl=(Lsl)context.getBean("lsl");
lsl.setInfo("lsl");
lsl.getWf().setInfo("lj");
lsl.print();
}
}
结果为lsllj
使用注解进行配置大致如此,当然仍需配置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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="bean"/>
</beans>
<context:component-scan base-package="bean"/>就代表自动扫描并装入Ioc容器。
@Configuration
通过使用注解 @Configuration 告诉 Spring ,这个 Class 是 Spring 的核心配置文件,并且通过使用注解 @Bean 定义 bean ,举例说明:
package com.spring.java_config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
//相当于在xml中声明一个Bean的id,但没有配置属性,属性自己调用setter设置
@Bean(name="animal")
public IAnimal getAnimal(){
return new Dog();
}
}
App.java 内容:
package com.spring.java_config;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
private static ApplicationContext context;
public static void main(String[] args) {
context = new AnnotationConfigApplicationContext(AppConfig.class);
IAnimal obj = (IAnimal) context.getBean("animal");
obj.makeSound();
}
}
在 ApplicationContext.xml 文件中只需要添加:
<bean id="animal" class="com.spring.java_conf">
即通过注解完成bean定义
@PostConstruct注解
声明方法,标明获取bean时一定初始化执行该方法
@PreDestroy 注解
声明方法,标明销毁bean时一定执行该方法
使用注解声明Bean方便很多,但是xml下可以直接配置Bean的属性,使用注解无法配置,只能先获取Bean再通过setter方法进行设置。
Spring Bean Scopes - Bean 的作用域
spring 中,Bean 的作用域决定了从 Spring 容器中返回的 Bean 实例的类型。在 Spring 中,支持以下 5 种类型的作用域:
- singleton — 单例模式,由 IOC 容器返回一个唯一的 bean 实例。
- prototype — 原型模式,被请求时,每次返回一个新的 bean 实例。
- request — 每个 HTTP Request 请求返回一个唯一的 Bean 实例。
- session — 每个 HTTP Session 返回一个唯一的 Bean 实例。
- globalSession — Http Session 全局 Bean 实例。
大多数情况下只需要处理 Spring 的核心作用域 — 单例模式( singleton )和原型模式( prototype ),默认情况下,作用域是单例模式。
使用模式只需要在对应的xml文件中配置bean时加入scope属性即可
例:该bean使用原型模式<bean id="Customer class="com.spring.customer.Customer" scope="prototype"/>
Spring Collections - Bean集合
要获取Bean的集合,就要使用Collections,使用时也需要进行相应的配置,主要类型有List,Set,Map,Properties等
配置bean时要显示添加,即增加property标签,具体格式一般为
<property name="集合变量名">
<集合类型(list/set等)>
<value>索引或与其特性有关的数据</value>
<bean>与相应集合绑定的bean信息配置</bean>
</集合类型>
</property>
例:
- <bean id="customerBean" class="com.spring.collections.Customer">
- <!-- java.util.List -->
- <property name="lists">//lists为在Customer中声明过的List的变量名
- <list>
- <value>1</value><!-- List 属性既可以通过 <value> 注入字符串,也可以通过 <ref> 注入容器中其他的 Bean-->
- <ref bean="personBean" />
- <value>2</value>
- <bean class="com.spring.collections.Person">
- <property name="name" value="List" />
- <property name="address" value="nmsl" />
- <property name="age" value="25" />
- </bean>
- </list>
- </property
Spring Bean 的生命周期
- Bean的建立, 由BeanFactory读取Bean定义文件,并生成各个实例
- Setter注入,执行Bean的属性依赖注入
- BeanNameAware的setBeanName(), 如果实现该接口,则执行其setBeanName方法
- BeanFactoryAware的setBeanFactory(),如果实现该接口,则执行其setBeanFactory方法
- BeanPostProcessor的processBeforeInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processBeforeInitialization()方法
- InitializingBean的afterPropertiesSet(),如果实现了该接口,则执行其afterPropertiesSet()方法
- Bean定义文件中定义init-method
- BeanPostProcessors的processAfterInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processAfterInitialization()方法
- DisposableBean的destroy(),在容器关闭时,如果Bean类实现了该接口,则执行它的destroy()方法 -Bean定义文件中定义destroy-method,在容器关闭时,可以在Bean定义文件中使用“destory-method”定义的方法
如果使用ApplicationContext来维护一个Bean的生命周期,则基本上与上边的流程相同,只不过在执行BeanNameAware的setBeanName()后,若有Bean类实现了org.springframework.context.ApplicationContextAware接口,则执行其setApplicationContext()方法,然后再进行BeanPostProcessors的processBeforeInitialization() 实际上,ApplicationContext除了向BeanFactory那样维护容器外,还提供了更加丰富的框架功能,如Bean的消息,事件处理机制等。
Bean过滤器
即标签<context:include-filter>与<context:exclude-filter>
<context:component-scan base-package="com.spring" >
<context:include-filter type="regex"
expression="com.spring.services.*Service.*" />
<context:exclude-filter type="regex"
expression="com.spring.dao.*DAO.*" />
</context:component-scan>
使用类正则的方式批量将class注入容器(或避免被注入容器)。