在我们学习某个框架时,首先应该明确三个问题:这个框架是为了解决什么问题而诞生的?这个框架的核心思想是什么?这个框架适合应用到哪些场景?其中思想是一个框架的灵魂,所以接下来我们将围绕Spring的设计思想从以下方面学习。
Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身。
一句话概括就是,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
轻量:从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
控制反转IoC:Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面Aop:Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
容器:Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
框架:Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
MVC:Spring的作用是整合,但不仅仅限于整合,Spring 框架可以被看做是一个企业解决方案级别的框架,Spring MVC是一个非常受欢迎的轻量级Web框架。
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是
BeanFactory
,它是工厂模式的实现。BeanFactory
使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.Spring 容器
2.IoC 概念
IoC全称是Inversion of Control,被翻译为控制反转,IoC是指程序中对象的获取方式发生反转,由最初的new方式创建,转变为通过描述(在Java中可以是XML或者注解)然后由第三方框架创建和获取特定对象的方式。
IoC按实现方法不同,可以分为依赖注入(DI全称是Dependency Injection,被翻译为依赖注入,DI的基本原理后续会详细介绍)和依赖查找两种,Spring容器是采用DI方式实现了IoC控制,IoC是Spring框架的基础和核心。
IoC是一种思想,而DI是实现IOC的主要技术和途径。
3.Spring 容器的简单使用
Spring IoC容器的设计主要基于BeanFactory和ApplicationContext这两个接口,其中ApplicationContext继承自BeanFactory接口,拥有更多的企业级的方法,下图为Spring相关的IoC容器的主要接口。
从图中可以看出BeanFactory位于设计最底层,所以它提供的方法和功能也较少,而ApplicationContext扩展了很多其他接口,功能十分强大,所以推荐使用ApplicationContext来实例化。
从本质上讲,BeanFactory和ApplicationContext仅仅只是一个维护Bean定义以及相互依赖关系的高级工厂接口而已,通过BeanFactory和ApplicationContext我们可以访问bean定义。
首先在容器配置文件applicationcontext.xml中添加Bean的定义
//定义bean,这样Spring IoC容器就能在初始化的时候找到它们
然后利用Application的实现类ClassPathXmlApplicationContext,创建BeanFactory和ApplicationContext容器对象
//加载文件系统的配置文件实例化
String config="applicationContext.xml所在的路径";
ApplicationContext ac = newFileSystemXmlApplicationContext(config);
//加载工程classpath下的配置文件实例化
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
最后,调用BeanFactory容器对象的getBean()方法获取Bean的实例即可
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("spring-cfg.xml");
JuiceMaker2 juiceMaker2 = (JuiceMaker2) ctx.getBean("juiceMaker2");
1.Bean 的初始化过程
上文简单的介绍了IoC 容器的简单使用,其实IoC 容器的初始化过程是远没有这么简单,但可以大致的理解一下其基本过程;Spring IoC容器的初始化有两大步骤:Bean的初始化和依赖注入。
Bean的初始化分为3步:
完成这3步后,Bean就在IoC容器上初始化了,接下来就得依赖注入其配置的资源,对于依赖注入,lazy-init配置选项表示是否初始化Bean,默认为false,即Spring会自动初始化不会懒加载;如果将其设置为true,那么 只有当我们使用IoC容器的 getBean()方法时才会进行初始化,完成依赖注入。
2.Bean 的实例化方法
将对象创建规则告诉Spring,Spring会帮你去创建对象:基于配置和默认规则,减少代码的书写。Spring容器创建Bean对象的方法有以下3种:
使用构造器实例化:
id或者name属性指定Bean名称,用于从Spring中查找这个Bean对象,class用于指定Bean类型,会自动调用无参构造器创建对象。
使用静态工厂方法实例化:
id属性用于指定Bean名称,class属性用于指定工厂类型,factory-method属性用于指定工厂中创建Bean对象的方法,必须用Static修饰的方法。
使用实例工厂方法实例化:
id用于指定Bean名称,factory-bean属性用于指定工厂Bean对象,factory-method属性用于指定工厂中创建Bean对象的方法
Bean 的命名:
在Spring容器中,每个Bean都需要有名字(即标识符),该名字可以用
为已定义好的Bean,再增加另外一个名字引用,可以使用
3.Bean 的生命周期
在IoC容器启动之后,并不会马上就实例化相应的bean,此时容器仅仅拥有所有对象的BeanDefinition(BeanDefinition:是容器依赖某些工具加载的XML配置信息进行解析和分析,并将分析后的信息编组为相应的BeanDefinition)。只有当getBean()调用时才是有可能触发Bean实例化阶段的活动。
简单来说就是:
基本生命周期流程如下:
- 容器启动,实例化所有实现了BeanFactoyPostProcessor接口的类。他会在任何普通Bean实例化之前加载。
- 实例化剩下的Bean,对这些Bean进行依赖注入(setter等)。
- 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法(实现BeanNameAware主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的)
- 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。(实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等)
- 如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,把y应用上下文作为参数传入.(作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanFactory里的参数BeanFactory )
- 如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法(作用是在Bean实例创建成功后进行增强处理,如对Bean进行修改,增加某个功能)
- 如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。
- 如果Bean配置有init属性,那么调用它属性中设置的方法
- 如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法(作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 )
- Bean正常使用(经过以上的工作后,Bean将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁)
- 如果Bean实现了DispostbleBean接口,Spring将调用它的destory方法,作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法。
- 如果Bean配置有destory属性,那么调用它属性中设置的方法
需要注意的是,Spring IoC容器的最低要求仅仅是实现BeanFactory接口,其他都属于额外功能需求。
上述8中的指定初始化回调方法init:
上述12中的指定销毁回调方法destroy:
//提示:指定销毁回调方法,仅使用于singleton模式的Bean并且Spring会管理对象的创建过程
在顶级的
下面以一个学生类Bean为例,来简单展示其生命周期的过程:
import org.springframework.beans.factory.BeanNameAware;
public class Student implements BeanNameAware {
private String name;
//无参构造方法
public Student() {
super();
}
/** 设置对象属性
* @param name the name to set
*/
public void setName(String name) {
System.out.println("设置对象属性setName()..");
this.name = name;
}
//Bean的初始化方法
public void initStudent() {
System.out.println("Student这个Bean:初始化");
}
//Bean的销毁方法
public void destroyStudent() {
System.out.println("Student这个Bean:销毁");
}
//Bean的使用
public void play() {
System.out.println("Student这个Bean:使用");
}
/* 重写toString
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Student [name = " + name + "]";
}
//调用BeanNameAware的setBeanName()
//传递Bean的ID。
@Override
public void setBeanName(String name) {
System.out.println("调用BeanNameAware的setBeanName()..." );
}
}
Bean的配置(applicationContext.xml):
使用Bean:
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class CycleTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
//Bean的使用
student.play();
System.out.println(student);
//关闭容器
((AbstractApplicationContext) context).close();
}
}
4.延迟实例化
在ApplicationContext实现的默认行为就是在启动时将所有singleton bean 提前实例化,如果不想让一个singleton bean在ApplicationContext 初始化时被提前实例化,可以使用
在顶级的
5.指定Bean依赖关系
当一个bean对另一个bean存在依赖关系时,可以利用
当一个bean对多个bean存在依赖关系时,depends-on属性可以指定多个bean名,用逗号隔开
在上述IoC概述中已经提到,IoC是一种设计思想,在实际环境中,实现IoC容器的方式主要有两大类:依赖查找和依赖注入。
而依赖注入DI是Spring IoC容器实现的主要技术,其可以分为3种方式:
构造器注入和setter注入是主要的方式,而接口注入是从别的地方注入的方式,比如在Web工程中,配置的数据源往往是通过服务器(tomcat)去配置,这个时候可以以JNDI的形式通过接口将他注入到Spring IoC容器来。
1.构造器注入
构造器注入依赖于构造方法实现,而构造方法可以是有参数的或者是无参数的。在大部分的情况下,我们都通过类的构造方法来创建类对象,Spring也可以采用反射的方式(反射的知识可见这篇文章),通过使用构造方法来完成注入,这就是构造器注入的原理。
为了让Spring完成对应的构造注入,我们有必要去描述具体的类、构造方法并设置对应的参数,这样Spring就会通过对应的信息用反射的形式创建对象。
以项目中常见的Dao层和Service层为例,Service层的对象往往需要使用Dao层的对象,那就需要Spring将Dao层的对象注入给它
public class UserServiceImp implements UserService {
private UserDao userDao;
private User user;
public UserServiceImp(UserDao userDao, User user) {
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
在Spring的配置文件中注册UserService,将UserDaoJdbc通过constructor-arg标签注入到UserService的有参数的构造方法,constructor-arg标签用于定义类构造方法的参数,通过name属性指定要注入的值,与构造方法参数列表参数的顺序无关,ref属性指向其它
2.setter 注入
setter方式是最简单的注入方式,也是Spring主流的注入方式,它消除了使用构造器注入时多个参数造成的可读性差问题。它可以把构造方法声明为无参数的,然后再通过 setter 注入为其设置对应的值,其原理也是通过反射实现的。
如上例,Spring将Dao层的对象和实体层Entity对象注入给Service层,配置如下:
需要注意的是,上面这两种写法都可以,Spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。
如果通过set方法注入属性,那么Spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
3.接口注入
public class ClassA {
private InterfaceB clzB;
public void doSomething() {
Ojbect obj = Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt();
}
}
在上述的代码中,ClassA依赖于InterfaceB的实现,我们如何获得InterfaceB的实现实例呢?传统的方法是在代码中创建 InterfaceB实现类的实例,并将赋予clzB.这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码。我们根据预先在配置文件中设定的实现类的类名(Config.BImplementation),动态加载实现类,并通过InterfaceB强制转型后为ClassA所用,这就是接口注入的一个最原始的雏形。
接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。
4.装配Bean
创建应用对象之间协作关系的的行为通常称为装配(wiring),beans 本身是一个大工厂,beans中的每一个bean就等于定义了一个组件,每个组件中就是我们具体的某个功能, 通过配置Spring容器来告诉它需要加载哪些Bean和如何装配这些Bean,如此才能保证他们彼此的协作。
在 Spring 中提供了 3 种方法进行配置:
在现实的工作中,这 3 种方式都会被用到,并且在学习和工作之中常常混合使用,所以这里给出一些关于这 3 种优先级的建议:
(1)通过 XML 配置装配 Bean
上述依赖注入的例子都是采用XML配置的方式装配Bean的,因为XML配置是很简单明了的,XML是最常见的Spring应用系统配置源。使用 XML 装配 Bean 需要定义对应的 XML,这里需要引入对应的 XML 模式(XSD)文件,这些文件会定义配置 Spring Bean 的一些元素,一个简单的 XML 配置文件如下:
这就只是一个格式文件,引入了一个 beans 的定义,引入了 xsd 文件,它是一个根元素,这样它所定义的元素将可以定义对应的 Spring Bean。
对于以上代码,class属性是一个类的全限定名,property
元素是定义类的属性,其中的 name
属性定义的是属性的名称,而 value
是它的值。
下半段表示注入自定义的类,这里先定义了一个 name
为 source 的 Bean,然后再制造器中通过 ref
属性去引用对应的 Bean,而 source 正是之前定义的 Bean 的 name
,这样就可以相互引用了。
装配集合:
有些时候我们需要装配一些复杂的东西,比如 Set、Map、List、Array 和 Properties 等,假设有一个复杂类如下:
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class ComplexAssembly {
private Long id;
private List list;
private Map map;
private Properties properties;
private Set set;
private String[] array;
/* setter and getter */
}
这个 Bean 没有任何的实际意义,只是为了介绍如何装配这些常用的集合类:
value-list-1
value-list-2
value-list-3
value-prop-1
value-prop-2
value-prop-3
value-set-1
value-set-2
value-set-3
value-array-1
value-array-2
value-array-3
(2)在Java中显示装配 Bean
上面,我们已经了解了如何使用 XML 的方式去装配 Bean,但是更多的时候已经不再推荐使用 XML 的方式去装配 Bean,更多的时候回考虑使用注解(annotation) 的方式去装配 Bean。
通过注解装配 Bean可以减少 XML 的配置项,采用了自动装配后,程序员所需要做的决断就少了,更加有利于对程序的开发,这就是“约定由于配置”的开发原则。
在 Spring 中,它提供了两种方式来让 Spring IoC 容器发现 Bean:
使用@Component装配Bean
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "student1")
public class Student {
@Value("1")
int id;
@Value("student_name_1")
String name;
// getter and setter
}
对于上述注解:
表示 Spring IoC 会把这个类扫描成一个 Bean 实例,而其中的 value
属性代表这个类在 Spring 中的 id
,这就相当于在 XML 中定义的 Bean 的 id:
,也可以简写成 @Component("student1")
,甚至直接写成 @Component
,对于不写的,Spring IoC 容器就默认以类名来命名作为 id
,只不过首字母小写,配置到容器中。
表示值的注入,跟在 XML 中写 value
属性是一样的,等同于以下代码:
但是现在我们声明了这个类,并不能进行任何的测试,因为 Spring IoC 并不知道这个 Bean 的存在,这个时候我们可以使用一个 StudentConfig 类去告诉 Spring IoC :
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class StudentConfig { }
这个类十分简单,没有任何逻辑,但是需要说明两点:
代表进行扫描,默认是扫描当前包的路径,扫描所有同一包下且带有 @Component
注解的实体类。
这样一来,我们就可以通过 Spring 定义好的 Spring IoC 容器的实现类——AnnotationConfigApplicationContext 去生成 IoC 容器了:
ApplicationContext context =
new AnnotationConfigApplicationContext(StudentConfig.class);
Student student = (Student) context.getBean(Student.class);
但由于 @ComponentScan
注解只是扫描当前所在包的 Java 类,以及通过 @Value
注解并不能注入对象,所以上述方法局限性很大,需要进行改进, @ComponentScan
注解提供了2个配置项来解决这个问题:
所以之前的辅助Spring IoC容器扫描的StudentConfig 类可以改为如下:
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = "com.spring.pojo需要扫描的包名")
public class StudentConfig { }
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackageClasses = com.spring.pojo.Student.class)
public class StudentConfig { }
对于 basePackages 和 basePackageClasses 的选择问题
basePackages 的可读性会更好一些,所以在项目中会优先选择使用它,但是在需要大量重构的工程中,尽量不要使用basePackages,因为很多时候重构修改包名需要反复地配置,而 IDE 不会给你任何的提示,而采用basePackageClasses会有错误提示。
(3)@Autowired自动装配
上面提到的两个弊端之一就是没有办法注入对象,通过自动装配我们将解决这个问题。
通过之前Spring IoC容器的学习,我们知道Spring是先完成Bean的定义和生成,然后再去寻找需要注入的资源,即在Spring生成了所有需要生成的Bean后,如果发现这个注解它就会在已有的Bean中查找相同的类型,找到后自动将其注入进来。
所谓自动装配技术是一种由 Spring 自己发现对应的 Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解 @Autowired
,这个时候 Spring 会根据类型去寻找定义的 Bean 然后将其注入。
1.先创建一个 StudentService 接口(使用接口是 Spring 推荐的方式,这样可以更为灵活,可以将定义和实现分离)
public interface StudentService {
public void printStudentInfo();
}
2.为上面的接口创建一个 StudentServiceImp 实现类
import org.springframework.beans.factory.annotation.Autowired;
import com.spring.pojo.Student;
@Component("studentService")
public class StudentServiceImp implements StudentService {
@Autowired
private Student student = null;
// getter and setter
public void printStudentInfo() {
System.out.println("学生的 id 为:" + student.getName());
System.out.println("学生的 name 为:" + student.getName());
}
}
该实现类实现了接口的 printStudentInfo() 方法,打印出成员对象 student 的相关信息,这里的 @Autowired
注解,表示在 Spring IoC 定位所有的 Bean 后,这个字段需要按类型注入,这样 IoC 容器就会寻找资源,然后将其注入。
// 第一步:修改 StudentConfig 类,告诉 Spring IoC 在哪里去扫描它:
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"pojo", "service"})//2个包位置
public class StudentConfig {
}
// 或者也可以在 XML 文件中声明去哪里做扫描
// 第二步:编写测试类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import pojo.StudentConfig;
import service.StudentService;
import service.StudentServiceImp;
public class TestSpring {
public static void main(String[] args) {
// 通过注解的方式初始化 Spring IoC 容器
ApplicationContext context =
new AnnotationConfigApplicationContext(StudentConfig.class);
StudentService studentService = context.getBean("studentService", StudentServiceImp.class);
studentService.printStudentInfo();
}
}
- 理解:
@Autowired
注解表示在 Spring IoC 定位所有的 Bean 后,再根据类型寻找资源,然后将其注入。- 过程: 定义 Bean —> 初始化 Bean(扫描) —> 根据属性需要从 Spring IoC 容器中搜寻满足要求的 Bean —> 满足要求则注入
- 问题: IoC 容器可能会寻找失败,此时会抛出异常(默认情况下,Spring IoC 容器会认为一定要找到对应的 Bean 来注入到这个字段,但有些时候并不是一定需要,比如日志)
- 解决: 通过配置项
required
来改变,比如@Autowired(required = false)
另外,@Autowired
注解不仅仅能配置在属性之上,还允许方法配置,常见的 Bean 的 setter 方法也可以使用它来完成注入,总之一切需要 Spring IoC 去寻找 Bean 资源的地方都可以用到,例如:
public class JuiceMaker {
......
@Autowired
public void setSource(Source source) {
this.source = source;
}
}
自动装配的歧义性(@Primary和@Qualifier)
在上面的例子中我们使用 @Autowired
注解来自动注入一个 Source 类型的 Bean 资源,但如果我们现在有两个 Srouce 类型的资源,Spring IoC 就会不知所措,不知道究竟该引入哪一个 Bean,如下
我们可以会想到 Spring IoC 最底层的容器接口——BeanFactory 的定义,它存在一个按照类型获取 Bean 的方法,显然通过 Source.class 作为参数无法判断使用哪个类实例进行返回,这就是自动装配的歧义性。
为了消除歧义性,Spring 提供了两个注解:
@Primary 注解:
代表首要的,当 Spring IoC 检测到有多个相同类型的 Bean 资源的时候,会优先注入使用该注解的类。该注解只是解决了首要的问题,但是并没有选择性的问题。
@Qualifier 注解:
上面所谈及的歧义性,一个重要的原因是 Spring 在寻找依赖注入的时候是按照类型注入引起的。除了按类型查找 Bean,Spring IoC 容器最底层的接口 BeanFactory 还提供了按名字查找的方法,如果按照名字来查找和注入就能消除歧义性。使用方法也很简单,直接指定名称即可:
public class JuiceMaker {
......
@Autowired
@Qualifier("source1")//表示注入的是此名称的类实例
public void setSource(Source source) {
this.source = source;
}
}
(4)使用@Bean装配Bean
不论是在Java中显示的装配Bean,还是自动装配,都需要@Component注解来声明这个类会被Spring IoC扫描成Bean实例,而且只能注解在类上,当你需要引用第三方包的(jar 文件)时,往往并没有这些包的源码,这时候将无法为这些包的类加入@Component注解,让它们变成开发环境可会被Spring IoC扫描的 Bean 资源。
这个时候有一种方法是自己创建一个新的类来继承扩展包里的类,然后在新类上使用@Component注解,但这样也显得太蠢了。
所以为了不显得那么蠢,Spring提供 @Bean
注解,注解到方法之上,并将方法返回对象作为 Spring 的 Bean 资源。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//@Configuration注解相当于XML文件的根元素,必须有,才能解析其中的 @Bean 注解
@Configuration
public class BeanTester {
//BeanTester类相当于封装文件,我们无法直接获得,但能调用其方法
@Bean(name = "testBean")
public String test() {
String str = "测试@Bean注解";
return str;
}
}
//让测试类在Spring IoC容器中获取这个Bean
// 在 pojo 包下扫描
ApplicationContext context = new AnnotationConfigApplicationContext("pojo");
// 因为这里获取到的 Bean 就是 testBean 方法返回的对象,String类型,直接打印
System.out.println(context.getBean("testBean"));
@Bean的4个配置项:
使用@Bean注解的好处就是能够动态获取一个 Bean 对象,能够根据环境不同得到不同的 Bean 对象。或者说将 Spring 和其他组件分离(其他组件不依赖 Spring,但是又想 Spring 管理生成的 Bean)。
(5)Bean的作用域
在默认的情况下,Spring IoC 容器只会对一个 Bean 创建一个实例(单例模式),但有时候,我们希望能够通过 Spring IoC 容器获取多个实例,我们可以通过 @Scope
注解或者
元素中的 scope
属性来设置,例如:
// XML 中设置作用域
// 使用注解设置作用域
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Spring Bean 中所说的作用域,在配置文件中即是“scope”,在面向对象程序设计中作用域一般指对象或变量之间的可见范围。而在Spring容器中是指其创建的Bean对象相对于其他Bean对象的请求可见范围。
对于singleton作用域和prototype作用域,Spring IoC容器的管理方式是不一样的:
对于作用域为prototype的bean,其destroy方法并没有被调用。如果bean的scope设为prototype时,当容器关闭时,destroy方法不会被调用。对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。
不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责(让Spring容器释放被prototype作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用)。
Spring容器可以管理singleton作用域下bean的生命周期,在此作用域下,Spring能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于prototype作用域的bean,Spring只负责创建,当容器创建了bean的实例后,bean的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。
参考文章:
《Java EE 互联网轻量级框架整合开发》
https://www.zhihu.com/question/21196869
https://blog.csdn.net/fuzhongmin05/article/details/73389779