Java bean之容器调用机制
作为java程序控制层数据交互部分的缓存,java bean的加载方式如上图所示,Spring容器根据xml <bean>标签的写入、java中的@configuration、@bean、@autowrite注解等信息,将相关的java bean的信息注册在容器的注册表中。之后Spring容器会根据注册表的信息将注册表中的bean类进行实例化,并将实例化后的bean对象与java程序中的bean实例绑定放入容器中,形成一个bean的缓存池,当java应用程序需要调用bean实例时,Spring容器会直接根据索引在缓存池中调用相应的bean实例。
1、bean的定义
bean的配置有三种方式:1、在xml文件中配置,2、使用注解定义并自动装配,3、基于java类提供Bean定义信息
基于xml文件的配置
对于基于XML的配置,Spring 2.0以后使用Schema的格式,使得不同类型的配置拥有了自己的命名空间,是配置文件更具扩展性。
①默认命名空间:它没有空间名,用于Spring Bean的定义;
②xsi命名空间:这个命名空间用于为每个文档中命名空间指定相应的Schema样式文件,是标准组织定义的标准命名空间;
③aop命名空间:这个命名空间是Spring配置AOP的命名空间,是用户自定义的命名空间。
命名空间的定义分为两个步骤:第一步指定命名空间的名称;第二步指定命名空间的Schema文档样式文件的位置,用空格或回车换行进行分分隔。
在Spring容器的配置文件中定义一个简要Bean的配置片段如下所示:
一般情况下,Spring IOC容器中的一个Bean即对应配置文件中的一个<bean>。其中id为这个Bean的标识,通过容器的getBean()即可获取对应的Bean,在容器中起到定位查找的作用,是外部程序和Spring IOC容器进行交互的桥梁。class属性指定了Bean对应的实现类。getBean的入参包括name、requiredType、args;其中name不可为空,对应的是xml文件中<bean>的id,requiredType可为空,对应的是<bean>标签的class,标明这个bean的类型,传入格式为类对象形式,即 类名.class形式。
使用注解配置信息启动spring容器
Spring提供了一个context的命名空间,它提供了通过扫描类包以应用注解定义Bean的方式:
<?xml version="1.0" encoding="UTF-8" ?> <!--①声明context的命名空间--> <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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" > <!--②扫描类包以应用注解定义的Bean--> <context:component-scan base-package="com.baobaotao.anno"/> <bean class="com.baobaotao.anno.LogonService"></bean> <!-- context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ --> <!-- context:component-scan base-package="com.baobaotao"> <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Dao"/> <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Service"/> <context:exclude-filter type="aspectj" expression="com.baobaotao..*Controller+"/> </context:component-scan --> </beans>
在①处声明context命名空间,在②处即可通过context命名空间的component-scan的base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里的所有类,并从类的注解信息中获取Bean的定义信息。
如果仅希望扫描特定的类而非基包下的所有类,你们可以使用resource-pattern属性过滤特定的类,如下所示:
< context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ >
这里我们将基类包设置为com.baobaotao,默认情况下resource-pattern属性的值为"**/*.class",即基类包里的所有类。这里我们设置为"anno/*.class",则Spring仅会扫描基包里anno子包中的类。
Bean注入
传统方式下是在XML中配置,此时分别有属性注入、构造函数注入和工厂方法注入
属性注入
属性注入即通过setXxx()方法注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入是实际应用中最常采用的注入方式。
属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; public class LogonService implements BeanNameAware{ private LogDao logDao; private UserDao userDao;
private String value;public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setLogDao(LogDao logDao) { this.logDao = logDao; }
public void setValue(String value) { this.value = value; }public LogDao getLogDao() { return logDao; } public UserDao getUserDao() { return userDao; }
public String getValue() {
return value;}
}
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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-autowire="byName" > <bean id="logDao" class="com.baobaotao.anno.LogDao"/> <bean id="userDao" class="com.baobaotao.anno.UserDao"/> <bean id="logonService" class="com.baobaotao.anno.LogonService"> <property name="logDao" ref="logDao"></property> <property name="userDao" ref="userDao"></property>
<property name="value" value="60000"></property></ bean > </ beans >
上述xml文件中,Spring JVM读取到相关配置后,首先将UserDao和LogDao进行bean实例化存放在容器中,然后再实例化logonService的时候,会根据property ref中的内容在容器中寻找匹配名称的实例bean注入到logonService的实例中,而value变量为基本类型变量,因此直接根据property中value的值注入logonService实例的变量value中。(注意:Spring只会检查bean中是否有setter方法,而是否有对应的属性变量则不做具体要求,但按照约定俗成的规则我们最好为其设定相应的属性变量。)个人理解中,属性注入的方式实际上是根据property中的name的值匹配bean类中的变量名从而进行值注入的
构造方法注入
使用构造函数注入的前提是Bean必须提供带参数的构造函数。例如
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; public class LogonService implements BeanNameAware{
private LogDao logDao; private UserDao userDao;
private String value;public LogonService(){} public LogonService(LogDao logDao, UserDao userDao,Sting value) { this.logDao = logDao; this.userDao = userDao;
this.value = Value;} public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setLogDao(LogDao logDao) { this.logDao = logDao; }
public void setValue(String value) { this.value = value; }public LogDao getLogDao() { return logDao; } public UserDao getUserDao() { return userDao; }
public String getValue() {
return value;}}
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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-autowire="byName"> <bean id="logDao" class="com.baobaotao.anno.LogDao"/> <bean id="userDao" class="com.baobaotao.anno.UserDao"/> <bean class="com.baobaotao.anno.LogonService"> <constructor-arg ref="logDao"></constructor-arg> <constructor-arg ref="userDao"></constructor-arg>
<constructor-arg index="2" type="int"><value>200</value> </constructor-arg></ bean > </ beans >
利用构造器方式注入bean分类可以有四种方式,根据类型匹配入参、根据索引匹配入参、索引类型联合匹配、自身类型反射入参。
上例中xml <constructor-arg ref="logDao"></constructor-arg>即是反射入参方式。如果bean构造函数入参的类型是可辨别的,由于java反射机制可以获取构造函数入参的类型,即使构造函数的注入不提供类型和索引的信息,Spring依旧可以完成构造函数信息的注入。因为之前实例化了logDao和userDao的bean,因此可以根据其id用ref指引到其在容器中匹配的实例。<constructor-arg index="2" type="String"><value>200</value> </constructor-arg>则是典型的联合匹配,其中index为变量在构造器中入参的索引位置,代表第index个入参(起始索引为0),type为入参的类型,value标签中为注入的bean的值。
工厂方法注入
非静态工厂方法:
有些工厂方法是非静态的,即必须实例化工厂类后才能调用工厂放。
package com.baobaotao.ditype; public class CarFactory { public Car createHongQiCar(){ Car car = new Car(); car.setBrand("红旗CA72"); return car; } public static Car createCar(){ Car car = new Car(); return car; } }
工厂类负责创建一个或多个目标类实例,工厂类方法一般以接口或抽象类变量的形式返回目标类实例,工厂类对外屏蔽了目标类的实例化步骤,调用者甚至不用知道具体的目标类是什么。
<?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-3.0.xsd"> <!-- 工厂方法--> <bean id="carFactory" class="com.baobaotao.ditype.CarFactory" /> <bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar"> </bean> </beans>
由于createHongQiCar为非静态方法,因此在调用该方法的时候需要有一个其所在类的实例,上述xml中 <bean id="carFactory" class="com.baobaotao.ditype.CarFactory" />即为实例化该类,随后<bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar">用factory-bean元素指定调用的实例,再用factory-method指定调用的方发,从而创建新的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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="car6" class="com.baobaotao.ditype.CarFactory" factory-method="createCar"></bean> </beans>
上述xml运作原理与非静态方法原理类似,只是调用的方法是静态方法createCar,因此只需要用class指定方法所在类,再用factory-bean指定调用的静态方法即可。
2、使用注解的方式
使用注解定义Bean
我们知道,Spring容器成功启动的三大要件分别是:Bean定义信息、Bean实现类以及Spring本身。如果采用基于XML的配置,Bean定义信息和Bean实现类本身是分离的,而采用基于注解的配置方式时,Bean定义信息即通过在Bean实现类上标注注解实现。
下面是使用注解定义一个DAO的Bean:
package com.baobaotao.anno; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; //①通过Repository定义一个DAO的Bean @Component("userDao") public class UserDao { }
在①处,我们使用@Component注解在UserDao类声明处对类进行标注,它可以被Spring容器识别,Spring容器自动将POJO转换为容器管理的Bean。
它和以下的XML配置是等效的:
<bean id="userDao" class="com.baobaotao.anno.UserDao"/>
除了@Component以外,Spring提供了3个功能基本和@Component等效的注解,它们分别用于对DAO、Service及Web层的Controller进行注解,所以也称这些注解为Bean的衍型注解:(类似于xml文件中定义Bean<bean id=" " class=" "/>
- @Repository:用于对DAO实现类进行标注;
- @Service:用于对Service实现类进行标注;
- @Controller:用于对Controller实现类进行标注;
之所以要在@Component之外提供这三个特殊的注解,是为了让注解类本身的用途清晰化,此外Spring将赋予它们一些特殊的功能。
使用注解配置信息启动spring容器
Spring提供了一个context的命名空间,它提供了通过扫描类包以应用注解定义Bean的方式:
<?xml version="1.0" encoding="UTF-8" ?> <!--①声明context的命名空间--> <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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" > <!--②扫描类包以应用注解定义的Bean--> <context:component-scan base-package="com.baobaotao.anno"/> <bean class="com.baobaotao.anno.LogonService"></bean> <!-- context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ --> <!-- context:component-scan base-package="com.baobaotao"> <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Dao"/> <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Service"/> <context:exclude-filter type="aspectj" expression="com.baobaotao..*Controller+"/> </context:component-scan --> </beans>
在①处声明context命名空间,在②处即可通过context命名空间的component-scan的base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里的所有类,并从类的注解信息中获取Bean的定义信息。
如果仅希望扫描特定的类而非基包下的所有类,你们可以使用resource-pattern属性过滤特定的类,如下所示:
< context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ >
这里我们将基类包设置为com.baobaotao,默认情况下resource-pattern属性的值为"**/*.class",即基类包里的所有类。这里我们设置为"anno/*.class",则Spring仅会扫描基包里anno子包中的类。
基于java类提供Bean定义
在普通的POJO类中只要标注@Configuration注解,就可以为spring容器提供Bean定义的信息了,每个标注了@Bean的类方法都相当于提供了一个Bean的定义信息。
package com.baobaotao.conf; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //①将一个POJO标注为定义Bean的配置类 @Configuration public class AppConf { //②以下两个方法定义了两个Bean,以提供了Bean的实例化逻辑 @Bean public UserDao userDao(){ return new UserDao(); } @Bean public LogDao logDao(){ return new LogDao(); } //③定义了logonService的Bean @Bean public LogonService logonService(){ LogonService logonService = new LogonService(); //④将②和③处定义的Bean注入到LogonService Bean中 logonService.setLogDao(logDao()); logonService.setUserDao(userDao()); return logonService; } }
①处在APPConf类的定义处标注了@Configuration注解,说明这个类可用于为Spring提供Bean的定义信息。类的方法处可以标注@Bean注解,Bean的类型由方法返回值类型决定,名称默认和方法名相同,也可以通过入参显示指定Bean名称,如@Bean(name="userDao").直接在@Bean所标注的方法中提供Bean的实例化逻辑。
在②处userDao()和logDao()方法定义了一个UserDao和一个LogDao的Bean,它们的Bean名称分别是userDao和logDao。在③处,又定义了一个logonService Bean,并且在④处注入②处所定义的两个Bean。
因此,以上的配置和以下XML配置时等效的:
<bean id="userDao" class="com.baobaotao.anno.UserDao"/> <bean id="logDao" class="com.baobaotao.anno.LogDao"/> <bean id="logService" class="com.baobaotao.conf.LogonService" p:logDao-ref="logDao" p:userDao-ref="userDao"/>
基于java类的配置方式和基于XML或基于注解的配置方式相比,前者通过代码的方式更加灵活地实现了Bean的实例化及Bean之间的装配,但后面两者都是通过配置声明的方式,在灵活性上要稍逊一些,但是配置上要更简单一些。
Bean的注解方式注入
使用@Autowired进行自动注入
Spring通过@Autowired注解实现Bean的依赖注入,下面是一个例子:
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; //① 定义一个Service的Bean(不需要在XML中定义Bean) @Service public class LogonService implements BeanNameAware{ //② 分别注入LogDao及UserDao的Bean(不需要在XML中定义property属性注入) @Autowired(required=false) private LogDao logDao; @Autowired @Qualifier("userDao") private UserDao userDao; public LogDao getLogDao() { return logDao; } public UserDao getUserDao() { return userDao; } public void setBeanName(String beanName) { System.out.println("beanName:"+beanName); } public void initMethod1(){ System.out.println("initMethod1"); } public void initMethod2(){ System.out.println("initMethod2"); } }
在①处,我们使用@Service将LogonService标注为一个Bean,在②处,通过@Autowired注入LogDao及UserDao的Bean。@Autowired默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入到@Autowired标注的变量中。
使用@Autowired的required属性
如果容器中没有一个和标注变量类型匹配的Bean,Spring容器启动时将报NoSuchBeanDefinitionException的异常。如果希望Spring即使找不到匹配的Bean完成注入也不用抛出异常,那么可以使用@Autowired(required=false)进行标注:
@Service public class LogonService implements BeanNameAware{ @Autowired(required=false) private LogDao logDao; ... }
默认情况下,@Autowired的required属性的值为true,即要求一定要找到匹配的Bean,否则将报异常。
使用@Qualifier指定注入Bean的名称
如果容器中有一个以上匹配的Bean时,则可以通过@Qualifier注解限定Bean的名称,如下所示:
@Service public class LogonService implements BeanNameAware{ @Autowired(required=false) private LogDao logDao;
//①注入名为UserDao,类型为UserDao的Bean @Autowired @Qualifier("userDao") private UserDao userDao; }
这里假设容器有两个类型为UserDao的Bean,一个名为userDao,另一个名为otherUserDao,则①处会注入名为userDao的Bean。
对类方法进行标注
@Autowired可以对类成员变量及方法进行标注,下面我们在类的方法上使用@Autowired注解:
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class LogonService implements BeanNameAware{ private LogDao logDao; private UserDao userDao; @Autowired public void setLogDao(LogDao logDao) { this.logDao = logDao; } @Autowired @Qualifier("userDao") public void setUserDao(UserDao userDao) { System.out.println("auto inject"); this.userDao = userDao; } }
上例中,当所有的bean被定义加载完毕存储在容器中后,实例化LongService的时候容器会调用@Autowired标注的方法在容器中寻找与入参类型匹配的bean注入到LongService的实例中。
如果一个方法拥有多个入参,在默认情况下,Spring自动选择匹配入参类型的Bean进行注入。Spring允许对方法入参标注@Qualifier以指定注入Bean的名称,如下所示:
@Autowired public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao){ System.out.println("multi param inject"); this.userDao = userDao; this.logDao =logDao; }
在以上例子中,UserDao的入参注入名为userDao的Bean,而LogDao的入参注入LogDao类型的Bean。
一般情况下,在Spring容器中大部分的Bean都是单实例的,所以我们一般都无须通过@Repository、@Service等注解的value属性为Bean指定名称,也无须使用@Qualifier按名称进行注入。
对标准注解的支持
此外,Spring还支持@Resource和@Inject注解,这两个标准注解和@Autowired注解的功能类型,都是对类变量及方法入参提供自动注入的功能。@Resource要求提供一个Bean名称的属性,如果属性为空,则自动采用标注处的变量名或方法名作为Bean的名称。
package com.baobaotao.anno; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import org.springframework.stereotype.Component; @Component public class Boss { private Car car; public Boss(){ System.out.println("construct..."); } @Resource("car") private void setCar(Car car){ System.out.println("execute in setCar"); this.car = car; } }
这时,如果@Resource未指定"car"属性,则也可以根据属性方法得到需要注入的Bean名称。可见@Autowired默认按类型匹配注入Bean,@Resource则按名称匹配注入Bean。而@Inject和@Autowired一样也是按类型匹配注入的Bean的,只不过它没有required属性。可见不管是@Resource还是@Inject注解,其功能都没有@Autowired丰富,因此除非必须,大可不必在乎这两个注解。(类似于Xml中使用<constructor-arg ref="logDao"></constructor-arg>或者<property name="logDao" ref="logDao"></property>进行注入,如果使用了@Autowired或者Resource等,这不需要在定义Bean时使用属性注入和构造方法注入了)
关于Autowired和@Resource
1.@Autowired注入是按照类型注入的,只要配置文件中的bean类型和需要的bean类型是一致的,这时候注入就没问题。但是如果相同类型的bean不止一个,此时注入就会出现问题,Spring容器无法启动。
2.@Resourced标签是按照bean的名字来进行注入的,如果我们没有在使用@Resource时指定bean的名字,同时Spring容器中又没有该名字的bean,这时候@Resource就会退化为@Autowired即按照类型注入,这样就有可能违背了使用@Resource的初衷。所以建议在使用@Resource时都显示指定一下bean的名字@Resource(name="xxx")
让@Resource和@Autowired生效的几种方式
1.在xml配置文件中显式指定
<!-- 为了使用Autowired标签,我们必须在这里配置一个bean的后置处理器 --> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" /> <!-- 为了使用@Resource标签,这里必须配置一个后置处理器 --> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
2.在xml配置文件中使用context:annotation-config 它是对已注册Bean的进行操作的配置,也就是说,Bean需要首先通过某种方式(比如Xml配置,或者其他注解)被注册,然后使用这个配置,可以对已注册的Bean进行进一步操作(比如注入到某个类的内部),也就是说,这个配置是用于“激活”已注册的Bean的,让已注册的Bean开始工作。
<context:annotation-config />
3.在xml配置文件中使用context:component-scan 首先有和<context:annotation-config/>
一样的作用,此外,它还可以扫描指定包下的类,将拥有注解的它们注册到Spring中。
<context:component-scan base-package="com.baobaotao.anno"/>
如果用<context:annotation-config/>
,我们还需要配置Xml注册Bean,而使用<context:component-scan />
的话,注册的步骤都免了,当然前提是我们对需要扫描的类使用的注解(比如@Componet,@Service),而如果同时使用两个配置的话,<context:annotation-config/>
会被忽略掉。
参考引入:spring中bean配置和bean注入