Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术自己,它的理念包括 IoC (Inversion of Control,控制反转) 和 AOP(Aspect Oriented Programming,面向切面编程),它是一个轻量级的 DI / IoC 和 AOP 容器的开源框架,来源于 Rod Johnson 在其著做 《Expert one on one J2EE design and development》 中阐述的部分理念和原型衍生而来。java
Spring的功能web
上面简短的介绍了一下Spring框架,下面正式开始讲述标题的内容面试
IOC:Inverse of Control(控制反转),控制反转不是什么技术,而是一种设计思想,在IOC以前,咱们想得到一个对象,就经过new关键字,那在IOC就是将本来在程序中手动建立对象的控制权,交由Spring框架来管理。在xml文件中配置bean的信息或者配置自动扫描包等,把程序中的类装载到Spring容器中管理。spring
那么IOC有什么好处?(面试)数据库
这个问题其实就是问咱们与new相比,IOC的优势在哪里。编程
假设咱们如今有一个Animal接口,咱们须要得到一些动物的叫声,新建一个Cat实现类数组
//Animal接口 package dto; public interface Animal { void cry(); } //Cat实现类 package dto; public class Cat implements Animal { public void cry() { System.out.println("I am Cat!"); } } //测试类 import dto.Animal; import dto.Cat; import org.junit.Test; public class testSpring { @Test public void test(){ Animal animal = new Cat(); animal.cry(); } }
那若是咱们如今想得到狗的叫声怎么办,新建一个实现类Dog缓存
package dto; public class Dog implements Animal{ public void cry() { System.out.println("I am Dog!"); } }
而后将测试代码改为Animal animal = new Dog(),其他代码不变。这是传统使用new实例化对象,若是程序大量使用的new来实例化对象,一但程序须要改动或者扩展应用,那开发者就太难受了。安全
下面咱们来看看使用spring来实现上述例子session
上面的Animal接口和Cat、Dog实现类不变,增长spring.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 id="cat" class="dto.Cat" /> </beans>
测试类以下
import dto.Animal; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring.xml"}) public class testSpring { @Autowired private Animal animal; @Test public void test(){ animal.cry(); } }
运行结果
那如今我须要获得Dog的叫声,测试代码不须要任何修改,只须要修改spring.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 id="dog" class="dto.Dog" /> </beans>
运行结果:
从上面的例子咱们能够看出spring的强大,经过依赖注入的方式咱们能够将实例化对象配置在xml文件,而后使用接口自动装配咱们实例化对象,这样作的好处:1、松耦合 2、便于扩展
DI:Dependency Injection(依赖注入),上面的ioc是一种思想,那么具体的实现呢就是依赖注入。好比A对象须要一个B对象,那么咱们能够经过在xml文件中配置或者经过注解等方式将其交给spring容器管理,这样当系统运行时,spring会在适当的时候将B注入给A对象,这样就完成了对象之间的依赖关系,至于B是怎么构造的,什么时候构造的,A不须要关心。
简单来讲一句话,DI(依赖注入)其实就是IOC的另一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:得到依赖对象的方式反转了。
Spring容器负责建立应用程序中的bean并经过DI来协调这些对象之间的关系。做为开发人员,须要告诉spring要建立哪些bean而且如何将其装配在一块儿,spring中装配Bean的三种主要方式:
三种方法的优先性
spring提供了三种方式来装配Bean,可是咱们优先使用自动配置机制。显式的配置越少越好。当你必需要显示配置Bean的时候(好比有些源码不是由你来维护的,当你须要为这些代码配置Bean的时候),推荐使用类型安全而且比XML更增强大的JavaConfig。最后,只有当你想要使用便利的XML空间,而且在JavaConfig中没有一样的实现时,才应该使用XML。
优先性:自动装配->Java显示配置->XML配置
使用 XML 装配 Bean 须要定义对应的 XML,这里须要引入对应的 XML 模式(XSD)文件,这些文件定义了配置 Spring 的XML元素,使用idea建立XML文件:
一个简单的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"> </beans>
在上面的XML文件中,<beans>是该模式中的一个元素,它是全部Spring配置文件的根元素。
装配简单属性值
下面是一个简单的例子
package dto; public class Student { private String name; private int age; /* setter and getter */ }
<bean id="student" class="dto.Student"> <property name="name" value="沈腾"/> <property name="age" value="40"/> </bean>
简单解释一下:
/
开头。也可使用name属性来代替id属性,甚至能够不写id属性,例如 <bean class="dto.Student"> <property name="name" value="沈腾"/> <property name="age" value="40"/> </bean>
这个时候,Spring将会根据"全限定类名#{number}"来进行命名。在这里就是“dto.Student#0”。其中#0是一个计数的形式,用来区分相同类型的其余bean,当第二次声明一个没有id属性的bean时,就会是“dto.Student#1”,这种自动化命名很方便,可是若是你后续须要用到这个bean的引用,那仍是经过id属性进行明确的命名。
class
属性显然就是一个类的全限定名property
元素是定义类的属性,其中的 name
属性定义的是属性的名称,而 value
是它的值。装配集合类型
上述是装配简单的类型变量,下面来演示如何装配集合类型,新建一个CollectionType类。
package dto; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class CollectionType { private Integer id; private List<String> list; private Map<String, String> map; private Properties properties; private Set<String> set; private String[] array; /* setter and getter */ }
在XML中该如何装配它们
<bean id="collectionType" class="dto.CollectionType"> <!-- 装配Integer类型的id --> <property name="id" value="1"/> <!-- 装配List类型的list --> <property name="list"> <list> <value>list-1</value> <value>list-2</value> <value>list-3</value> </list> </property> <!-- 装配Map类型的map --> <property name="map"> <map> <entry key="key1" value="value-1"/> <entry key="key2" value="value-2"/> <entry key="key3" value="value-3"/> </map> </property> <!-- 装配Properties类型的properties --> <property name="properties"> <props> <prop key="prop1">value-prop-1</prop> <prop key="prop2">value-prop-2</prop> <prop key="prop3">value-prop-3</prop> </props> </property> <!-- 装配Set类型的set --> <property name="set"> <set> <value>set-1</value> <value>set-2</value> <value>set-3</value> </set> </property> <!-- 装配String[]类型的array --> <property name="array"> <array> <value>array-1</value> <value>array-2</value> <value>array-3</value> </array> </property> </bean>
复杂类型的装配大概就是这些,更复杂的类型均可以进行分解,例如list中的对象能够不是一个基本类型,而是一个自定义的类
例如List 属性使用 <list>
元素定义注入:
<property name="list"> <list> <ref bean="beanOne"/> <ref bean="beanTwo"/> </list> </property>
Map 属性使用 <map>
元素定义注入:
<property name="map"> <map> <entry key-ref="keyBean" value-ref="valueBean"/> </map> </property>
Set 属性使用 <set>
元素定义注入:
<property name="set"> <set> <ref bean="bean"/> </set> </property>
除了上述的配置以外,spring还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和 XML 模式(XSD)文件。使用c-命名空间须要在XML的顶部声明其模式:以下所示
这里将c-命名空间和经过构造器注入初始化bean方在一块儿讲
上面其实就是Spring根据XML中的配置而后利用反射类的实例对象调用setter方法来实现属性注入,若是你将类中的setter方法去掉,运行时就会报错
下面经过构造器参数的方式实现属性注入:
具体到构造器注入,有两种选择:<constructor-arg>元素或者使用Spring3.0所引入的c-命名空间
继续以Student类为例
package dto; public class Student { private String name; private int age; public Student(String name, int age){ this.name = name; this.age = age; } /* setter and getter */ }
下面对比一下两种不一样的方式实现
<!-- 使用<constructor-arg>元素 --> <bean id="student" class="dto.Student"> <constructor-arg name="name" value="沈腾"/> <constructor-arg name="age" value="40"/> </bean> <!-- 引入c-命名空间以后 --> <bean id="student1" class="dto.Student" c:name="沈腾" c:age="40"/>
c-命名空间属性名以 “c:
” 开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此以后若是须要注入对象的话则要跟上 -ref
(如c:cd-ref="card"
,则对cd这个构造器参数注入以前配置的名为 card 的 bean)
很显然,使用 c-命名空间属性要比使用 <constructor-arg>
元素精简,可是它直接引用了构造器参数的名称,这不利于安全性,所以咱们可使用下面这种方式
<bean id="student1" class="dto.Student" c:_0="沈腾" c:_1="40"/>
咱们将参数的名称替换成了 “0” 和 “1” ,也就是参数的索引。由于在 XML 中不容许数字做为属性的第一个字符,所以必需要添加一个下划线来做为前缀。
在构造器实现属性注入的方式有c-命名空间来替代<constructor-arg>,那么p-命名空间则是属性注入<property>元素的替代方案,首先在XML头部进行声明:
<!-- 使用<property>元素 --> <bean id="student" class="dto.Student"> <property name="name" value="沈腾"/> <property name="age " value="40" /> <property name="card" ref="card"> </bean> <!-- 引入p-命名空间以后 --> <bean id="student1" class="dto.Student" p:name="沈腾" p:age="40" p:card-ref="card"/>
使用p命名空间的方式和c-命名空间很像,先以p:开头,后面是属性名和属性值,若是属性须要注入的是一个对象,则须要在属性名后面加上-ref代表要注入的是一个Bean的引用
util-命名空间的出现是由于p-命名空间不能用来装配集合,所以在装配集合的时候就没有一种便利的方式,首先仍是在头部声明其格式:
接下来看一下util-命名空间的使用:
首先在Student类中添加一个list对象
package dto; import java.util.List; public class Student { private int id; private String name; private List<String> list; /* sttter and getter */ }
<!-- 仅仅使用p-命名空间 --> <bean id="student1" class="dto.Student" p:name="沈腾" p:id="40"> <property name="list"> <list> <value>value-1</value> <value>value-2</value> <value>value-3</value> </list> </property> </bean> <!-- 使用util-命名空间后 --> <util:list id="list"> <value>value-1</value> <value>value-2</value> <value>value-3</value> </util:list> <bean id="student2" class="dto.Student" p:name="沈腾" p:id="40" p:list-ref="list" />
使用util-命名空间后能够将list移出到bean的外面,并将其声明到独特的bean之中
<util:list>
只是 util-命名空间中的多个元素之一,下表提供了 util-命名空间提供的全部元素:
元素 | 描述 |
---|---|
<util:constant> |
引用某个类型的 public static 域,并将其暴露为 bean |
<util:list> |
建立一个 java.util.List 类型的 bean,其中包含值或引用 |
<util:map> |
建立一个 java.util.map 类型的 bean,其中包含值或引用 |
<util:properties> |
建立一个 java.util.Properties 类型的 bean |
<util:property-path> |
引用一个 bean 的属性(或内嵌属性),并将其暴露为 bean |
<util:set> |
建立一个 java.util.Set 类型的 bean,其中包含值或引用 |
Spring从两个角度来实现自动化装配:
首先咱们来看看组件扫描,建立一个表示动物类型接口animal:
package dto; public interface animal { void cry(); }
而后咱们再新建一个cat类实现animal:
package dto; import org.springframework.stereotype.Component; @Component public class Cat implements animal{ public void cry() { System.out.println("我是cat"); } }
在cat中咱们使用了@Component注解,这个简单的注解代表该类会做为组件类,并告知Spring要为这个类建立bean。这样就不要显示的配置Cat bean,由于你已经使用了@Component注解,Spring会为咱们处理好。
可是组件扫描默认是不启用的,咱们还须要显示配置一下Spring,命名它去寻找带有@Component注解,并为其建立bean,下面来建立一个配置类CatConfig
package dto; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class CatConfig { }
简单解释一下:
上面@Configuration注解表示这是一个配置类,@ComponentScan注解,这个注解可以在Spring中启用组件扫描,若是没有其余配置的话,@ComponentScan注解默认会扫描与配置类相同的包,也就是dto包以及这个包下面全部的子包,查找带有@Component注解的类,而且会在Spring中自动为其建立一个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" 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 http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="dto" /> </beans>
下面新建一个Test1来测试一下
import dto.Cat; import dto.CatConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.assertNotNull; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes= CatConfig.class) public class test1 { @Autowired private Cat cat; @Test public void catShouldNotNull(){ assertNotNull(cat); } }
代码运行是绿色表示经过,以前配置bean时都有指定id,咱们使用 @Component注解类是并无指定id,那么Spring将根据类名指定一个ID,那Cat类的id就是cat,也就是将类名的第一个字母变成小写,固然咱们也能够去指定id名,列如:
package dto; import org.springframework.stereotype.Component; @Component("bigCat") public class Cat implements animal{ public void cry() { System.out.println(); } }
还有另一种为bean命名的方式,不是使用@Component注解,而是使用Java依赖注解规范中提供的@Named注解,上面的@Component注解能够替换成@Named注解,二者有细微的差别,可是大多数状况下,两者能够互换。了解就好,开发的时候推荐使用@Component注解
设置组件扫描的基础包
@ComponenetScan有两个属性:basePackages和basePackageClasses
上述使用@Component注解没有指定包,那么Spring会默认以配置类所在的包做为基础包来扫描组件,固然咱们也能够指定包名
package dto; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("dto") public class CatConfig { }
咱们也可使用basePackages明确指定设置基础包
package dto; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = "dto") public class CatConfig { }
basePackages是一个复数形式,它能够容许咱们指定多个基础包,以逗号隔开
package dto; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = {"dto","Controller","Service"}) public class CatConfig { }
在上面的例子中,所设置的基础包是以String类型表示的,可是这种方法是类型不安全的,一但咱们重构代码,包名改变了,那么指定的基础包可能就会出错。这是就可使用@basePackageClasses属性,列如
package dto; import Controller.baseController; import Service.baseService; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackageClasses = {Cat.class, baseController.class, baseService.class}) public class CatConfig { }
咱们不在使用String类型名称来指定包,俄日basePackageClasses属性设置的数组中包含了类,这些类所在的包将会做为组件扫描的基础包。
如何在应用程序中全部的对象都是相互独立的,彼此之间没有依赖,那组件扫描就足够知足要求了,可是不少对象会依赖其余对象才能完成任务,这样咱们就须要有一种方法将组件扫描获得的bean和它们的依赖装配在一块儿,因此来了解一下自动装配
自动装配———@Autowired
简单来讲,自动装配就是让Spring自动知足bean依赖的一种方法,在知足依赖的过程当中,会在Spring应用的上下文中寻找匹配某个bean需求的其余bean。为了声明要进行自动装配,咱们须要借助Spring的@Autowired注解
首先在Service中新建一个接口:
package Service; public interface CatService { public void printInfo(); }
而后为上面接口编写一个实现类:
package Service; import dto.Cat; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("catService") public class CatServiceImp implements CatService{ @Autowired private Cat cat; @Override public void printInfo() { cat.cry(); } }
上面要实现的就是将cat对象实现自动装配
//第一步:修改CatConfig文件,将Service包也加入到组件扫描中去 package dto; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = {"dto","Service"}) public class CatConfig { } //第二步:编写测试类 import Service.CatService; import dto.CatConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes= CatConfig.class) public class test1 { @Autowired private CatService catService; @Test public void test(){ catService.printInfo(); } }
运行结果:
@Autowired注解不只仅能配置在属性之上,还容许用在构造器,Setter方法或者任何方法上:
//第一种,做用在属性上 @Autowired private Cat cat; //第二种,做用在构造器上 private Cat cat; @Autowired public CatServiceImp(Cat cat){ this.cat = cat; } //第三种,做用在属性的setter方法上 private Cat cat; @Autowired public void setCat(Cat cat){ this.cat = cat; } //第四种,做用在一个普通方法上 private Cat cat; @Autowired public void insertCat(Cat cat){ this.cat = cat; }
只要任何能注入这个cat对象的方法均可以使用@Autowired,推荐使用这种自动装配来完成依赖注入,这样会使得配置文件大幅度减小,知足约定优于配置的原则,加强程序的健壮性。
自动装配的歧义性(@Primary和@Qualifier)
咱们在上面的例子中新建一个Animal接口的实现类dog,
//Animal接口 package dto; public interface Animal { void cry(); } //Dog实现类 package dto; import org.springframework.stereotype.Component; @Component("dog") public class Dog implements Animal { public void cry() { System.out.println("I am dog"); } } //Cat实现 package dto; import org.springframework.stereotype.Component; @Component("cat") public class Cat implements Animal { public void cry() { System.out.println("I am Cat"); } } //测试类 import dto.Animal; import dto.CatConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes= CatConfig.class) public class test1 { @Autowired private Animal animal; @Test public void test(){ animal.cry(); } }
idea直接就提示报错,说有两个bean
//使用Qualifier注解 public class test1 { @Autowired @Qualifier("cat") private Animal animal; @Test public void test(){ animal.cry(); } } //使用@Primary注解 @Component("cat") @Primary public class Cat implements Animal { public void cry() { System.out.println("I am Cat"); } }
使用组件扫描和自动化配置是推荐使用的方式,可是有时候自动化方案行不通,好比说,你想将第三方库中的组件装配到你的应用中,在种状况下,是没有办法在它的类中添加@Component和@Autowired注解的。所以必须使用显示的配置,XML配置咱们上面已经讲过了,下面来经过@Bean装配bean
首先新建一个配置类而且使用@Bean注解
package dto; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class StudentConfig { @Bean public Student createStudent(){ return new Student(); } }
@Configuration注解代表这个一个配置类,能够用来代替XML文件,而后在createStudent方法上使用bean注解,@Bean注解会告诉Spring这个方法会返回一个对象,这个对象要注册为Spring应用上下文中Bean。默认状况下,bean的ID就是带有@Bean注解的方法名,在本例中,bean的方法名是createStudent。
测试类
import dto.Student; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import static org.junit.Assert.assertNotNull; public class testSpring { @Test public void test(){ //从Java配置中加载应用上下文,并扫描dto包 ApplicationContext context = new AnnotationConfigApplicationContext("dto"); Student s = (Student) context.getBean("createStudent"); assertNotNull(s) ; } }
运行结果是绿色,证实s对象已经被建立。固然也可使用@Bean(name="student")指定bean的名字
Bean的做用域
在默认的状况下,Spring IoC 容器只会对一个 Bean 建立一个实例,但有时候,咱们但愿可以经过 Spring IoC 容器获取多个实例,咱们能够经过 @Scope
注解或者 <bean>
元素中的 scope
属性来设置,例如:
// XML 中设置做用域 <bean id="" class="" scope="prototype" /> // 使用注解设置做用域 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Spring 提供了 5 种做用域,它会根据状况来决定是否生成新的对象:
做用域类别 | 描述 |
---|---|
singleton(单例) | 在Spring IoC容器中仅存在一个Bean实例 (默认的scope) |
prototype(多例) | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时 ,至关于执行new XxxBean():不会在容器启动时建立对象 |
request(请求) | 用于web开发,将Bean放入request范围 ,request.setAttribute("xxx") , 在同一个request 得到同一个Bean |
session(会话) | 用于web开发,将Bean 放入Session范围,在同一个Session 得到同一个Bean |
globalSession(全局会话) | 通常用于 Porlet 应用环境 , 分布式系统存在全局 session 概念(单点登陆),若是不是 porlet 环境,globalSession 等同于 Session |
参考资料