整理过spring的IOC原理之后,要关注下spring如何实现这个IOC的控制反转。
那什么是IOC呢?可以参见
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以进行零配置实现IOC。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息可以直接以注解的形式定义在实现类中,从而达到零配置的目的。
1.使用XML配置的方式实现IOC
IBookDAO接口如下:
/*
* 图书数据访问接口
* */
public interface IBookDAO {
/*
* 添加图书
* */
public String addBook(String bookName);
}
BookDAO实现类如下:
pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhangguo</groupId>
<artifactId>Spring051</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring051</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
</dependencies>
</project>
业务类BookService如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/*
* 图书业务类
* */
public class BookService {
IBookDAO bookDAO;
public BookService(){
//容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBean01.xml");
//从容器中获得id为bookdao的bean
bookDAO = (IBookDAO) ctx.getBean("bookdao");
}
public void storeBook(String bookname){
System.out.println("图书上货");
String result=bookDAO.addBook(bookname);
System.out.println(result);
}
}
容器的配置文件IOCBeans01.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookdao" class="com.zhangguo.Spring051.ioc01.BookDAO"></bean>
</beans>
测试类Test如下:
public class Test {
@org.junit.Test
public void testStoreBook()
{
BookService bookservice=new BookService();
bookservice.storeBook("《Spring MVC权威指南 第一版》");
}
}
运行结果:
1.使用无参构造方法创建对象
如下所示,则上下文会使用无参构造方法创建对象
<?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="tom" class="spring02.Student"></bean>
</beans>
2.使用有参构造方法创建对象
Person.java
/**人*/
public abstract class Person {
public String name;
}
Student.java
/**学生*/
public class Student extends Person {
/**身高*/
public int height;
/**有参构造方法*/
public Student(String name,int height){
this.name=name;
this.height=height;
}
@Override
public String toString() {
return "Student{" + "height=" + height+",name="+name +'}';
}
}
School.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class School {
public static void main(String[] args) {
//IoC容器
ApplicationContext ctx=
new ClassPathXmlApplicationContext("bookbean01.xml","beans02.xml");
//从容器中获取对象
Person tom=ctx.getBean("tom",Person.class);
System.out.println(tom);
}
}
配置文件beans02.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="tom" class="spring02.Student">
<constructor-arg name="name" value="张柏川"></constructor-arg>
<constructor-arg name="height" value="195"></constructor-arg>
</bean>
</beans>
注意:如果在使用构造方法时不想通过参数名称指定参数则可以直接使用索引,如:
<bean id="rose" class="spring02.Student">
<constructor-arg index="0" value="张柏芝"></constructor-arg>
<constructor-arg index="1" value="196"></constructor-arg>
</bean>
3.通过属性赋值
/**地址*/
public class Address {
/**国家*/
private String country;
/**城市*/
private String city;
public Address() {
}
public Address(String country, String city) {
this.country = country;
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"country='" + country + '\'' +
", city='" + city + '\'' +
'}';
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
配置文件bean02.xml
<bean name="zhuhai" class="spring02.Address">
<property name="country" value="中国"></property>
<property name="city" value="珠海"></property>
</bean>
测试代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class School {
public static void main(String[] args) {
//IoC容器
ApplicationContext ctx=
new ClassPathXmlApplicationContext("bookbean01.xml","beans02.xml");
//从容器中获取对象
Person tom=ctx.getBean("rose",Person.class);
Address zhuhai=ctx.getBean("zhuhai",Address.class);
System.out.println(zhuhai);
}
}
4.对象引用
可以用id和name指定bean的标志符
import com.yucheng.cmis.test.Address;
public abstract class Person {
/**姓名*/
public String name;
/**地址*/
public Address address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
配置文件
<?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 name="zhuhai" class="spring02.Address">
<property name="country" value="中国"></property>
<property name="city" value="珠海"></property>
</bean>
<bean id="tom" class="spring02.Student">
<constructor-arg name="name" value="张柏川"></constructor-arg>
<constructor-arg name="height" value="195"></constructor-arg>
<constructor-arg name="address" ref="zhuhai"></constructor-arg>
</bean>
<bean id="rose" class="spring02.Student">
<constructor-arg index="0" value="张柏芝"></constructor-arg>
<constructor-arg index="1" value="196"></constructor-arg>
<constructor-arg index="2" ref="zhuhai"></constructor-arg>
</bean>
</beans>
测试代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class School {
public static void main(String[] arg){
ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean01.xml","beans02.xml");
//从容器中获取对象
Person tom=ctx.getBean("tom",Person.class);
Person rose=ctx.getBean("rose",Person.class);
System.out.println(tom);
System.out.println(rose);
}
}
5.对象作用域
从容器中取回的对象默认是单例的:
Person roseA=ctx.getBean("rose",Person.class);
Person roseB=ctx.getBean("rose",Person.class);
//Address zhuhai=ctx.getBean("zhuhai",Address.class);
System.out.println(tom);
System.out.println(roseA==roseB);
运行结果:
使用scope属性可以指定作用域
<bean id="rose" class="spring02.Student" scope="prototype">
<constructor-arg index="0" value="张柏芝"></constructor-arg>
<constructor-arg index="1" value="196"></constructor-arg>
<constructor-arg index="2" ref="zhuhai"></constructor-arg>
</bean>
测试代码:
//从容器中获取对象
Person tom=ctx.getBean("tom",Person.class);
Person roseA=ctx.getBean("rose",Person.class);
Person roseB=ctx.getBean("rose",Person.class);
//Address zhuhai=ctx.getBean("zhuhai",Address.class);
System.out.println(tom);
System.out.println(roseA==roseB);
运行结果:
6.延迟初始化Bean
ApplicationContext实现的默认行为就是在启动时将所有的singleton bean提前进行实例化。通常这样的实例化方式是好事,因为配置中或者运行环境的错误就会被立即发现,否则可能会花几个小时或者几天,如果不想这样,可以将单例bean定义为延迟加载防止它提前实例化。延迟初始化bean会告诉IOC容器在第一次需要的时候才实例化而不是在容器启动时才实例化。
在xml配置文件中,延迟初始化通过<bean>元素的lazy-init属性进行控制,比如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
7.回调方法
1).初始化回调方法
配置文件:
<bean id="tom" class="spring02.Student" init-method="init" destroy-method="over">
<constructor-arg name="name" value="张柏川"></constructor-arg>
<constructor-arg name="height" value="195"></constructor-arg>
<constructor-arg name="address" ref="zhuhai"></constructor-arg>
</bean>
student类:
import com.yucheng.cmis.test.Address;
import com.yucheng.cmis.test.sub.Person;
public class Student extends Person{
/**身高*/
public int height;
/**有参构造方法*/
public Student(String name,int height){
this.name=name;
this.height=height;
}
/**有参构造方法*/
public Student(String name,int height,Address address){
this.name=name;
this.height=height;
this.address=address;
}
@Override
public String toString() {
return "Student{" + "height=" + height+",name="+name +'}'+address;
}
public void init(){
System.out.println("对象被创建");
}
public void over(){
System.out.println("对象被回收");
}
}
运行结果:
2)析构回调函数
实现org.springframework.beans.factory.DisposableBean接口,允许一个bean当容器需要其销毁时获得一次回调,DisposableBean 接口也只规定一个方法:
void destroy() throws Exception;
建议不使用DisposableBean回调接口,因为会与Spring耦合,使用@PreDestroy注解或者指定一个普通的方法,但能由bean定义支持。基于XML配置的元数据,使用<bean/>的destroy-method属性。例如,下面的定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
示例:
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
与下面的效果相同:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
示例:
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是不与spring耦合。
使用spring注解配置IOC
上一个示例是通过传统的XML配置完成IOC的,如果内容比较多则配置需花费很多时间,通过注解可以减轻工作量,但注解后修改要麻烦一些,耦合度会增加,应该根据需要选择合适的方法。
1.修改BookDAO
import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; /** * 图书数据访问实现类 */ @Component("bookdaoObj") public class BookDAO implements IBookDAO { public String addBook(String bookname) { return "添加图书"+bookname+"成功!"; } }
在类上增加一个注解Component,在类的开头引用一个@Component的注解,它可以被Spring容器识别,启动Spring后,会自动把它转成容器管理的Bean。
除了@Component外,Spring提供了3个基本功能和@component等效的注解,分别对应于用于对DAO、Service和Controller进行注解。
(1)@Repository用于对DAO实现类进行注解。
(2)@Service用于对业务层的注解,但是目前该功能和@Component相同。
(3)@Controller用于对控制层的注解,但是目前该功能和@Component相同。
2.修改BookService
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yucheng.cmis.test.IBookDAO;
public class BookService {
IBookDAO bookDAO;
public void storeBook(String bookName){
//容器
ApplicationContext ctx=new ClassPathXmlApplicationContext("IOCBeans02.xml");
//从容器中获得id为bookdao的beanbookDAO=(IBookDAO)ctx.getBean("bookdaoObj");
System.out.println("图书上货");
String result=bookDAO.addBook(bookName);
System.out.println(result);
}
}
在构造方法中的代码直接写在了storeBook方法中,避免循环加载的问题。
3.修改IOC配置文件IOCBeans02.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:p="http://www.springframework.org/schema/p"
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-4.3.xsd">
<context:component-scan
base-package="com.zhangguo.Spring051.ioc02"></context:component-scan>
</beans>
粗体字是新增的xml命名空间与模式约束文件位置。增加注解扫描的范围,指定一个包,可以通过属性设置更加精确的范围如:
<context>标记常用属性配置:
resource-pattern:对指定的基包下面的子包进行选取。
<context>子标记:
include-filter:指定需要包含的包
exclude-filter:指定需要排除的包
<!--自动扫描com.zhangguo.anno.bo中的类进行扫描>
<context:component-scan base-package="com.zhangguo.anno" resource-pattern="bo/*.class" />
<context:component-scan base-package="com.zhangguo.anno" >
<context:include-filter type="aspectj“ expression="com.zhangguo.anno.dao.*.*"/>
<context:exclude-filter type=“aspectj” expression=“com.zhangguo.anno.entity.*.*”/>
</context:component-scan>
include-filter表示需要包含的目标类型,exclude-filter表示需要排除的目标类型,type表示采的过滤类型,共有5种类型:
Filter Type Example ExpressionDescriptionannotation org.example.SomeAnnotation注解了SomeAnnotation的类 assignable org.example.SomeClass 所有扩展或者实现SomeClass的类aspectJ org.example..*Service+ AspectJ语法表示org.example包下所有包含Service的类及其子类 regex org\.example\.Default.* Regelar Expression,正则表达式 custom org.example.MyTypeFilter 通过代码过滤,实现org.springframework.core.type.TypeFilter接口
expression表示过滤的表达式:
<!-- 1、如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类 -->
<context:component-scan base-package="com.zhangguo.Spring051"
resource-pattern="ioc04/A*.class">
</context:component-scan>
只扫描com.zhangguo.Spring051.ioc04下所有的名称以A开始的类。
<!--2、扫描注解了org.springframework.stereotype.Repository的类
exclude-filter表示排除,include-filter表示包含,可以有多个-->
<context:component-scan base-package="com.zhangguo.Spring051.ioc04">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository" />
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<!--3、aspectj类型,扫描dao下所有的类,排除entity下所有的类--> <context:component-scan base-package="com.zhangguo.anno" > <context:include-filter type="aspectj" expression="com.zhangguo.anno.dao.*.*"/> <context:exclude-filter type="aspectj" expression="com.zhangguo.anno.entity.*.*"/> </context:component-scan>
测试类:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { @org.junit.Test public void testStoreBook(){ //容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans02.xml"); BookService bookservice = ctx.getBean(BookService.class); bookservice.storeBook("《Spring MVC权威指南 第二版》"); } }
运行结果:
IbookDao接口:
public interface IBookDao {
void add();
}
BookDao实现类:
import org.springframework.stereotype.Component;
@Component("bookdao")
public class BookDao implements IBookDao {
public void add() {
System.out.println("新增图书成功!");
}
}
MSBookDao实现类:
public class MSBookDao implements IBookDao {
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("新增图书到SQLServer成功!");
}
}
BookService实现类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BookService {
public static void main(String[] args) {
//容器
ApplicationContext ctx=
new ClassPathXmlApplicationContext(new String[]{"bookbean11.xml"});
//从容器中获得对象
IBookDao dao=ctx.getBean("bookdao",IBookDao.class);
dao.add();
}
}
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:p="http://www.springframework.org/schema/p"
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-4.3.xsd">
<bean name="tom" class="spring02.Student" p:name="tom" p:height="224"/>
<!--指定要扫描的包,如果有多个可以用逗号隔开-->
<context:component-scan base-package="spring11">
<!--使用正则排除以B开头的类-->
<context:exclude-filter type="regex" expression="spring11\.B.*"></context:exclude-filter>
</context:component-scan>
</beans>
指定的两个bookdao是不正确的,重名了,但是因为在组件扫描中我们排除了一个所有也可以正确运行。
5.作用域用scope来指定
默认容器中bean是单例的:
//从容器中获得对象
IBookDao dao1=ctx.getBean("bookdao",IBookDao.class);
IBookDao dao2=ctx.getBean("bookdao",IBookDao.class);
System.out.println(dao1==dao2);
结果:
修改成原型:
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component("bookdao") @Scope("prototype") public class MSBookDao implements IBookDao { public void add() { System.out.println("新增图书到SQLServer成功!"); } }结果:
用来指定bean的作用域
singleton--单例 只创建一个对象
prototype--原型 想创建多少个就创建多少个
request--针对web项目,不同的请求创建单独的Bean对象,同一个请求共享一个Bean。
session--针对web项目,不同的会话创建单独的Bean对象,同一个会话共享一个Bean。
默认名称:
如果使用component对象时不指定名称,基于@component及其扩展(如@Service和自定义等)标注和classpath-scan定义的Bean,注解有一个value属性,如果提供了,那么就此Bean的名字。如果不提供,就会使用Spring默认的命名机制,即简单类名且第一个字母小写。
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component() //未指定名称 @Scope("prototype") public class MSBookDao implements IBookDao { public void add() { System.out.println("新增图书到SQLServer成功!"); } }
测试:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BookService { public static void main(String[] args) { //容器 ApplicationContext ctx= new ClassPathXmlApplicationContext(new String[]{"bookbean11.xml"}); //从容器中获得对象 IBookDao dao1=ctx.getBean("MSBookDao",IBookDao.class); IBookDao dao2=ctx.getBean("MSBookDao",IBookDao.class); System.out.println(dao1==dao2); dao1.add(); } }结果:
在基于XML的配置中bean标签还有很多属性,如scope、lazy、init-method、depends-on、qualify等。
从容器中获取实例时也可以根据类型获取
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; @Component public class BookStore { @Autowired BookService service; public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean12.xml"); BookStore store =ctx.getBean(BookStore.class); store.service.addNewBook(); A a=ctx.getBean("a",A.class); System.out.println(a); } } @Component class A{ } @Component class B extends A{ }结果:
小结:
从配置文件中我们可以看出我们并没有声明bookdaoObj与BookService类型的对象,但还是从容器中获得了实例并成功运行了,原因是:在类的开头使用了@component注解,它可以被spring容器识别,启动spring后,会自动把它转成容器管理的Bean。
自动装配:
从上一个实例中可以看出有两个位置都使用了ApplicationContext初始化后获得需要的bean,可以通过自动装配简化。
修改BookDAO类:
import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; /** * 图书数据访问实现类 */ @Repository public class BookDAO implements IBookDAO { public String addBook(String bookname) { return "添加图书"+bookname+"成功!"; } }把注释修改成Repository,比component更贴切一些,非必要。
修改BookService
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Service; /** * 图书业务类 */ @Service public class BookService { @Autowired IBookDAO bookDAO; public void storeBook(String bookname){ System.out.println("图书上货"); String result=bookDAO.addBook(bookname); System.out.println(result); } }将类BookService上的注解替换成Service,在bookDao成员变量上加一个注解@Autowired,该注解的作用是:可以对成员变量、方法和构造函数进行注解,来完成自动装配的工作,通俗来说就是会根据类型从容器中自动查到一个Bean给bookDAO字段。@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier。另外可以使用其他注解,@Resource:等同于@Qualifier,@Inject:等同于@Autowired。
@Service用于注解业务层组件(我们通常定义的service层就用这个)
@Controller用于注解控制层组件(如Struts中的action)
@Repository用于注解数据访问组件,即DAO组件
@Component反之组件,当组件不好归类的时候,我们可以使用这个注解进行注解。
装配注解主要:@Autowired、@Qualifier、@Resource,它们的特点是:
1、@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;
2、@Autowired默认是按照类型装配注入的,如果想按照名称来装配注入,则需要结合@Qualifier一起使用;
3、@Resource注解是由J2EE提供的,而@Autowired是由spring提供,故减少系统对spring的依赖建议使用@Resource的方式;如果Maven项目是1.5的JRE则需换成更高版本的。
4、@Resource和@Autowired都可以书写注解字段或者该字段的setter方法之上
5、@Autowired可以对成员变量、方法以及构造函数进行注释,而@Qualifier的注解对象是成员变量、方法入参、构造函数入参。
6、@Qualifier("XXX")中的XX是Bean的名称,所以@Autowired和@Qualifier结合使用时,自动注入的策略就从byType转变成byName。
7、@Autowired注释进行自动注入时,Spring容器中匹配的候选Bean数目必须有且仅有一个,通过属性required可以设置为非必需。
8、@Resource装配顺序
8.1如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
8.2如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
8.3如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个都会抛出异常
8.4如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则退回为一个原始类型进行匹配,如果匹配则自动装配;
import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Service; /** * 图书业务类 */ @Service public class BookService { public IBookDAO getDaoofbook() { return daoofbook; } /* @Autowired @Qualifier("bookdao02") public void setDaoofbook(IBookDAO daoofbook) { this.daoofbook = daoofbook; }*/ @Resource(name="bookdao02") public void setDaoofbook(IBookDAO daoofbook) { this.daoofbook = daoofbook; } /* @Autowired @Qualifier("bookdao02") */ IBookDAO daoofbook; /* public BookService(@Qualifier("bookdao02") IBookDAO daoofbook) { this.daoofbook=daoofbook; }*/ public void storeBook(String bookname){ System.out.println("图书上货"); String result=daoofbook.addBook(bookname); System.out.println(result); } }测试运行
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { @org.junit.Test public void testStoreBook() { //容器 ApplicationContext ctx=new ClassPathXmlApplicationContext("IOCBeans03.xml"); BookService bookservice=ctx.getBean(BookService.class); bookservice.storeBook("《Spring MVC权威指南 第三版》"); } }运行结果:
简单示例:
/**图书数据访问接口*/ public interface IBookDao { /**添加新书*/ void save(String name); }BookDao
import org.springframework.stereotype.Repository; /** * 完成图书数据访问 */ @Repository public class BookDao implements IBookDao { public void save(String name) { System.out.println("添加图书" + name + "到数据库成功!"); } }BookService
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BookService { @Autowired IBookDao bookDao; /**新增一本书*/ public void addNewBook(){ String bookname="《Spring MVC学习指南》"; bookDao.save(bookname); } }
配置bookBean.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:p="http://www.springframework.org/schema/p" 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-4.3.xsd"> <!--指定要扫描的包,如果有多个可以用逗号隔开--> <context:component-scan base-package="spring12"> </context:component-scan> </beans>BookStore
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; @Component public class BookStore { @Autowired BookService service; public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean12.xml"); BookStore store =ctx.getBean(BookStore.class); store.service.addNewBook(); } }测试结果:
4.@Qualifier指定名称
@Qualifier("XXX")中XXX是Bean的名称,所以@Autowired和@Qualifier结合使用时,自动注入的策略就从byType转变成byName了。qualifier的意思是合格者,通过这个标识,表明了哪个实现类才是我们所需要的
先看下面这个示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; @Service public class QualifierTest { @Autowired IBookDao dao; public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean13.xml"); QualifierTest obj = ctx.getBean(QualifierTest.class); System.out.println(obj.dao); } } interface IBookDao { } @Repository class BookDaoA implements IBookDao { } @Repository class BookDaoB implements IBookDao { }
运行结果:
这样报错的原因是找到了多个Bean,spring不知道选择哪一个。使用Qualifier指定名称。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; @Service public class QualifierTest { @Autowired @Qualifier("daoA") IBookDao dao; public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean13.xml"); QualifierTest obj = ctx.getBean(QualifierTest.class); System.out.println(obj.dao); } } interface IBookDao { } @Repository("daoA") class BookDaoA implements IBookDao { } @Repository("daoB") class BookDaoB implements IBookDao { }
结果:
5.@Resource
@Resource装配顺序
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则跑出异常
如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个都会抛出异常
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class ResourceTest { @Resource(name = "carB") ICarDao dao; public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean13.xml"); ResourceTest obj = ctx.getBean(ResourceTest.class); System.out.println(obj.dao); } } interface ICarDao { } @Repository("carA") class CarDaoA implements ICarDao { } @Repository("carB") class CarDaoB implements ICarDao { } /* @Resource装配顺序 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配; */
运行结果:
6.多种注入方式
示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class InjectTest { //注入给构造方法 @Autowired public InjectTest(IUserDao dao2) { this.dao2=dao2; } //注入给成员变量,字段 @Resource IUserDao dao1; IUserDao dao2; IUserDao dao3; IUserDao dao4; //注入给属性 @Autowired public void setDao3(IUserDao dao3) { this.dao3 = dao3; } //注入给方法参数 @Autowired public void injectDao4(IUserDao dao4, IUserDao dao5) { this.dao4 = dao4; System.out.println(dao5); } public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean13.xml"); InjectTest obj = ctx.getBean(InjectTest.class); System.out.println(obj.dao1); System.out.println(obj.dao2); System.out.println(obj.dao3); System.out.println(obj.dao4); } } interface IUserDao { } @Scope("prototype") @Repository class UserDao implements IUserDao { }
结果:
零配置实现IOC
1.综合实例
所谓的零配置就是不再使用xml文件来初始化容器,使用一个类型来替代
IBookDAO代码:
/** * 图书数据访问接口 */ public interface IBookDAO { /** * 添加图书 */ public String addBook(String bookname); }
IBookDAO的实现类BookDAO代码如下:
import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; /** * 图书数据访问实现类 */ @Repository public class BookDAO implements IBookDAO { public String addBook(String bookname) { return "添加图书"+bookname+"成功!"; } }
在BookDAO类上注解了@Repository当初初试化时该类将被容器管理会生成一个Bean,可以通过构造方法测试。
业务层BookService代码如下:
import javax.annotation.Resource; import org.springframework.stereotype.Service; /** * 图书业务类 */ @Service public class BookService { @Resource IBookDAO bookDAO; public void storeBook(String bookname){ System.out.println("图书上货"); String result=bookDAO.addBook(bookname); System.out.println(result); } }
类BookService将对容器管理因为注解了@Service,初始化时会生成一个单例的Bean,类型为BookService。在字段bookDAO上注解了@Resource,用于自动装配,Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
新增一个用于替代原xml配置文件的ApplicationCfg类,代码如下:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * 容器的配置类 */ @Configuration @ComponentScan(basePackages="com.zhangguo.Spring051.ioc06") public class ApplicationCfg { @Bean public User getUser(){ return new User("成功"); } }
@Configuration相当于配置文件中的<bean/>,ComponentScan相当于配置文件中的context:context-scan,属性也一样设置,@Bean相当于<bean/>,只能注解在方法和注解上,一般在方法上使用,源码中描述:
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE}),方法名相当于id。中间使用到了User,User类的代码如下:
import org.springframework.stereotype.Component; @Component("user1") public class User { public User() { System.out.println("创建User对象"); } public User(String msg) { System.out.println("创建User对象"+msg); } public void show(){ System.out.println("一个学生对象!"); } }
初始化容器的代码与以前有一些不一样,具体如下:
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { @org.junit.Test public void testStoreBook() { //容器,注解配置应用程序容器,Spring通过反射ApplicationCfg.class初始化容器 ApplicationContext ctx=new AnnotationConfigApplicationContext(ApplicationCfg.class); BookService bookservice=ctx.getBean(BookService.class); bookservice.storeBook("《Spring MVC权威指南 第四版》"); User user1=ctx.getBean("user1",User.class); user1.show(); User getUser=ctx.getBean("getUser",User.class); getUser.show(); } }
容器的初始化通过一个类型完成,Spring通过反射ApplicationCfg.class初始化容器,中间user1和getUser是否为相同的Bean呢?
答案是否定的,因为在ApplicationCfg中声明的方法getUser相当于在xml文件中定义一个<bean id="getUser" class=".."/>,在User类上注解@Component("user1")相当于另一个<bean id="user1" class="..."/>.
运行结果:
小结:
使用零配置和注解虽然方便,不需要编写麻烦的xml文件,但并非为了取代xml,应该根据实例需要选择,或二者结合使用,毕竟使用一个类作为容器的配置信息是硬编码的,不好在发布后修改。
2.零配置,由注解指定实例
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Repository; public class NoXMLIoC { public static void main(String[] args) { //基于类型的配置 ApplicationContext ctx=new AnnotationConfigApplicationContext(AppCfg.class); ICarDao dao1=ctx.getBean(ICarDao.class); dao1.add("Spring Pro"); } } interface ICarDao{ void add(String name); } @Repository class CarDao implements ICarDao{ public void add(String name) { System.out.println("添加"+name+"成功!"); } } @Configuration @ComponentScan(basePackages = "spring14") class AppCfg{ }
运行结果
3.零配置,由方法指定实例
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Repository; public class NoXMLIoC { public static void main(String[] args) { //基于类型的配置 ApplicationContext ctx=new AnnotationConfigApplicationContext(AppCfg.class); ICarDao dao2=ctx.getBean("mysqlDao",ICarDao.class); dao2.add("Spring Pro"); } } interface ICarDao{ void add(String name); } class CarDao implements ICarDao{ public void add(String name) { System.out.println("添加"+name+"成功!"); } } @Configuration @ComponentScan(basePackages = "spring14") class AppCfg{ @Bean ICarDao mysqlDao(){ //方法名就是bean的name return new CarDao(); } }
运行结果:
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Repository; public class NoXMLIoC { public static void main(String[] args) { //基于类型的配置 ApplicationContext ctx=new AnnotationConfigApplicationContext(AppCfg.class); ICarDao dao1=ctx.getBean("oracleDao",ICarDao.class); dao1.add("Spring Pro Oracle"); ICarDao dao2=ctx.getBean("mysqlDao",ICarDao.class); dao2.add("Spring Pro MySQL"); System.out.println(dao1==dao2); } } interface ICarDao{ void add(String name); } @Repository("oracleDao") class CarDao implements ICarDao{ public void add(String name) { System.out.println("添加"+name+"成功!"); } } @Configuration @ComponentScan(basePackages = "spring14") class AppCfg{ @Bean ICarDao mysqlDao(){ //方法名就是bean的name return new CarDao(); } }
运行结果:
总结:
@Component要求被Spring容器管理,被Spring扫描,应用于不确定的功能(宽泛)
@Repository应用于数据访问层DAO
@Service应用于业务层
@Controller应用于控制层
注解要被Spring容器管理的类->配置文件指定要扫描的包->初始化容器,获得bean
@Autowired自动装配,字段(成员变量)、方法、属性、构造,不支持指定名称,配合@Qualifier
@Resource自动装配,指定名称,指定类型,不属于Spring java
@Qualifier在自动装配时指定对象的名称,避免多个不唯一的实例
@Autowired()
@Qualifier("z")
X x;
@Resource(name="z") X x;
@Component("y") Y:X
@Component("z") Z:X
至此,终于整理完,有待等待进一步吸收消化。。。