1.依赖注入(DI)的概念
所谓的依赖注入是指在运行期,由外部容器将依赖对象注入到组件中.依赖注入(DI)背后的基本原理是对象之间的依赖关系(即一起工作的其它对象).例如:Service业务层依赖Dao的提供的对象来实现业务逻辑,如果使用依赖注入技术的话,代码将更加清晰.而且当bean自己不再担心对象之间的依赖关系(甚至不知道依赖的定义指定地方和依赖的实际类)之后,实现更高层次的松耦合将易如反掌。DI主要有两种注入方式,即Setter注入和构造器注入.
2.手动装配--->注入依赖对象(全部基于.XML文件)
2.1 Setter注入
2.1.1 Setter注入的简单案例
通过调用无参构造器或无参static
工厂方法实例化bean之后,调用该bean的setter方法,即可实现基于setter的DI。
PersonDao.java
package cn.itcast.dao;
public interface PersonDao {
public abstract void add();
}
PersonDaoImpl.java
package cn.itcast.dao.impl;
import cn.itcast.dao.PersonDao;
public class PersonDaoImpl implements PersonDao {
/* (non-Javadoc)
* @see cn.itcast.dao.impl.PersonDao#add()
*/
@Override
public void add(){
System.out.println("This is a PersonDaoImpl.add() itcast method");
}
}
PersonService.java
package cn.itcast.service;
public interface PersonService {
public abstract void add();
}
PersonServiceImpl.java
package cn.itcast.service.impl;
import cn.itcast.dao.PersonDao;
import cn.itcast.service.PersonService;
public class PersonServiceImpl implements PersonService {
private PersonDao personDao;
public void add(){
System.out.println("This is a add() method");
personDao.add();
}
/**必须有set方法**/
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
}
<?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-2.5.xsd">
<!-- Dao层对象注入Service层 :依赖注入 inject-->
<!-- 正如你所看到的,bean类中的setter方法与xml文件中配置的属性是一一对应的 !-->
<bean id="personDao" class="cn.itcast.dao.impl.PersonDaoImpl"/>
<bean id="personService" class="cn.itcast.service.impl.PersonServiceImpl">
<property name="personDao" ref="personDao"></property>
</bean>
</beans>
测试结果:This is a add() method
This is a PersonDaoImpl.add() itcast method
2.1.1 编码实现Setter注入功能的代码
BeanDefinition.java
package cn.itcast.mycontext;
import java.util.ArrayList;
import java.util.List;
public class BeanDefinition {
private String id;
private String className;
List<PopertyDefinition> popertys = new ArrayList<PopertyDefinition>();
public List<PopertyDefinition> getPopertys() {
return popertys;
}
public void setPopertys(List<PopertyDefinition> popertys) {
this.popertys = popertys;
}
public BeanDefinition(String id, String clazz) {
this.id=id;
this.className = clazz;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
PopertyDefinition.java
package cn.itcast.mycontext;
public class PopertyDefinition {
private String name;
private String ref;
public PopertyDefinition(String name, String ref) {
this.name = name;
this.ref = ref;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
}
MyClassPathXmlApplicationContext.java
package cn.itcast.mycontext;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
public class MyClassPathXmlApplicationContext {
private List<BeanDefinition> BeanDefinitions = new ArrayList<BeanDefinition>();
/**Key:id Value:instaceBean **/
private Map<String,Object> sigletons = new HashMap<String,Object>();
public MyClassPathXmlApplicationContext(String filename){
this.readXml(filename);
this.instaceBeans();
this.injectBeans();
}
/**
* 注入Beans
*/
private void injectBeans() {
for(BeanDefinition beanDefinition:BeanDefinitions){
Object bean = sigletons.get(beanDefinition.getId());
if(bean!=null){
PropertyDescriptor[] pds;
try {
pds = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
for(PopertyDefinition popertyDefinition:beanDefinition.getPopertys()){
for(PropertyDescriptor pd:pds){
if(popertyDefinition.getName().equals(pd.getName())){
Method setter = pd.getWriteMethod();
if(setter!=null){
Object value = sigletons.get(popertyDefinition.getRef());
setter.setAccessible(true);
setter.invoke(bean, value);
}
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 实例化Bean
*/
private void instaceBeans() {
for(BeanDefinition beanDefinition:BeanDefinitions){
if(beanDefinition.getClassName()!=null && beanDefinition.getClassName().trim()!=""){
try {
sigletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance());
} catch (InstantiationException | IllegalAccessException
| ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
/**
* 读取配置Bean的XML文件
* dom4j-1.6.1.jar,jaxen-1.1-beta-6.jar
* @param filename
*/
private void readXml(String filename) {
SAXReader reader = new SAXReader();
Document document = null;
try{
URL url = this.getClass().getClassLoader().getResource(filename);
document = reader.read(url);
Map<String,String> nsMap = new HashMap<String,String>();
nsMap.put("ns","http://www.springframework.org/schema/beans");
XPath xsub = document.createXPath("//ns:beans/ns:bean");
xsub.setNamespaceURIs(nsMap);
@SuppressWarnings("unchecked")
List<Element> elements = xsub.selectNodes(document);
for(Element element : elements){
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
BeanDefinition beanDfine = new BeanDefinition(id,clazz);
xsub = document.createXPath("ns:property");
xsub.setNamespaceURIs(nsMap);
@SuppressWarnings("unchecked")
List<Element> propertys = xsub.selectNodes(element);
for(Element property:propertys){
String propertyName = property.attributeValue("name");
String propertyRef = property.attributeValue("ref");
PopertyDefinition propertyDefinition = new PopertyDefinition(propertyName, propertyRef);
beanDfine.getPopertys().add(propertyDefinition);
}
BeanDefinitions.add(beanDfine);
}
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取Bean实例
* @param beanName
* @return
*/
public Object getBean(String beanName){
return this.sigletons.get(beanName);
}
}
2.2 构造器注入
基于构造器的DI通过调用带参数的构造器来实现,每个参数代表着一个依赖。
修改PersonServiceImpl.java
package cn.itcast.service.impl;
import cn.itcast.dao.PersonDao;
import cn.itcast.service.PersonService;
public class PersonServiceImpl implements PersonService {
private PersonDao personDao;
public PersonServiceImpl(PersonDao personDao){
this.personDao = personDao;
}
public void add(){
System.out.println("This is a add() method");
personDao.add();
}
}
<?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-2.5.xsd">
<!-- 第一种方式
<bean id="personDao" class="cn.itcast.dao.impl.PersonDaoImpl"/>
<bean id="personService" class="cn.itcast.service.impl.PersonServiceImpl">
<constructor-arg>
<bean class="cn.itcast.dao.impl.PersonDaoImpl"></bean>
</constructor-arg>
</bean>
---->
<!-- 第二种方式 ---->
<bean id="personDao" class="cn.itcast.dao.impl.PersonDaoImpl"/>
<bean id="personService" class="cn.itcast.service.impl.PersonServiceImpl">
<constructor-arg index="0" type="cn.itcast.dao.PersonDao" ref="personDao"/>
</bean>
</beans>
//output:
This is a add() method
This is a PersonDaoImpl.add() itcast method
//~
2.3 Setter和构造器注入其他类型配置详解
2.1.1 直接变量(基本类型、Strings
类型等。)
<value/>
元素通过人可以理解的字符串来指定属性或构造器参数的值。正如前面所提到的,JavaBean PropertyEditor
将用于把字符串从java.lang.String
类型转化为实际的属性或参数类型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/mydb</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>masterkaoli</value>
</property>
</bean>
<property/>
和<constructor-arg/>
元素中也可以使用'value'
属性,这样会使我们的配置更简洁,比如下面的配置:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
Spring团队更倾向采用属性方式(使用<value/>
元素)来定义value值。当然我们也可以按照下面这种方式配置一个java.util.Properties
实例:
<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
看到什么了吗?如果采用上面的配置,Spring容器将使用JavaBean PropertyEditor
把<value/>
元素中的文本转换为一个java.util.Properties
实例。由于这种做法的简单,因此Spring团队在很多地方也会采用内嵌的<value/>
元素来代替value
属性。
2.1.2 引用其它的bean(协作者)
在<constructor-arg/>
或<property/>
元素内部还可以使用ref
元素。该元素用来将bean中指定属性的值设置为对容器中的另外一个bean的引用。如前所述,该引用bean将被作为依赖注入,而且在注入之前会被初始化(如果是singleton bean则已被容器初始化)。尽管都是对另外一个对象的引用,但是通过id/name指向另外一个对象却有三种不同的形式,不同的形式将决定如何处理作用域及验证。
第一种形式也是最常见的形式是通过使用<ref/>
标记指定bean
属性的目标bean,通过该标签可以引用同一容器或父容器内的任何bean(无论是否在同一XML文件中)。XML 'bean
'元素的值既可以是指定bean的id
值也可以是其name
值。
<ref bean="someBean"/>
第二种形式是使用ref的local
属性指定目标bean,它可以利用XML解析器来验证所引用的bean是否存在同一文件中。local
属性值必须是目标bean的id属性值。如果在同一配置文件中没有找到引用的bean,XML解析器将抛出一个例外。如果目标bean是在同一文件内,使用local方式就是最好的选择(为了尽早地发现错误)。
<ref local="someBean"/>
第三种方式是通过使用ref的parent
属性来引用当前容器的父容器中的bean。parent
属性值既可以是目标bean的id
值,也可以是name
属性值。而且目标bean必须在当前容器的父容器中。使用parent属性的主要用途是为了用某个与父容器中的bean同名的代理来包装父容器中的一个bean(例如,子上下文中的一个bean定义覆盖了他的父bean)。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <-- notice that the name of this bean is the same as the name of the'parent' bean
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <-- notice how we refer to the parent bean
</property>
<!-- insert other configuration and dependencies as required as here -->
</bean>
2.1.3. 内部bean
所谓的内部bean(inner bean)是指在一个bean的<property/>
或 <constructor-arg/>
元素中使用<bean/>
元素定义的bean。内部bean定义不需要有id或name属性,即使指定id 或 name属性值也将会被容器忽略。
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline --> <property name="target">
<bean class="com.example.Person">
<!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
注意:内部bean中的scope
标记及id
或name
属性将被忽略。内部bean总是匿名的且它们总是prototype模式的。同时将内部bean注入到包含该内部bean之外的bean是不可能的。
2.1.4. 集合
通过<list/>
、<set/>
、<map/>
及<props/>
元素可以定义和设置与Java Collection
类型对应List
、Set
、Map
及Properties
的值。
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property> <!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry>
<key>
<value>an entry</value>
</key>
<value>just some string</value>
</entry>
<entry>
<key>
<value>a ref</value>
</key>
<ref bean="myDataSource" />
</entry>
</map>
</property> <!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
注意:map的key或value值,或set的value值还可以是以下元素:
bean | ref | idref | list | set | map | props | value | null
3.手动装配--->注入依赖对象(基于.XML文件和注解)
基于注解的配置方式,使用BeanPostProcessor
与注解是 Spring IoC 容器的一个普通扩展方法。例如,Spring 2.0 对必须的属性引入了@Required注解。在 Spring 2.5中已经可以用注解的方式去驱动 Spring 的依赖注射了。更重要的是,@Autowired
注解提供功能,并且提供了更细致的控制与更好的适应性。Spring 2.5 也支持 JSR-250 中的一些注解,例如@Resource
,@PostConstruct
,以及@PreDestroy
。当然,要使注解可用,您必须使用 Java 5 (Tiger)或更新的版本,以使得可以访问源代码层次的注解。这些注解可以被注册为独立 bean 的定义,但它们也可以被隐式地注册,通过基于 XML 的配置方式,如下例(请注意包含 'context
' 命名空间):
<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
</beans>
PS:还能够解决配置文件中的字段过多的问题.减少配置文件的压力.
3.1 @Resource (common-annotations.jar)
Spring 也提供了使用 JSR-250 bean 属性支持的注射方式。这是一种在 Java EE 5 与 Java 6 中普遍使用的方式(例如,在 JSF 1.2 中映射 beans 或者 JAX-WS 2.0 端点),对于Spring 托管的对象 Spring 可以以这种方式支持映射。
@Resource
有一个‘name’属性,缺省时,Spring 将这个值解释为要注射的 bean 的名字。换句话说,如果遵循by-name的语法,如下例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有显式地给出名字,缺省的名字将继承于字段名或者 setter 方法名:如果是字段名,它将简化或者等价于字段名;如果是 setter 方法名,它将等价于 bean 属性名。下面这个例子使用名字 "movieFinder" 注射到它的 setter 方法:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
<bean id="movieFinder" class="MovieFinder"/>
<bean id="simpleMovieLister " class="SimpleMovieLister "/>
</beans>
注意:注解提供的名字将被BeanFactory
解析为 bean 名。请注意,这些名字也可能通过 JNDI 被解析(需要配置 Spring 的SimpleJndiBeanFactory
)。不过,建议您依靠缺省行为与 Spring 的 JNDI 查找功能。
与@Autowired
类似,@Resource
可以回退为与标准 bean 类型匹配(例如,使用原始类型匹配取代特殊命名 bean)来解决著名的"resolvable dependencies":BeanFactory
接口,ApplicationContext
接口,ResourceLoader
接口,ApplicationEventPublisher
接口以及 MessageSource
接口。请注意:这只有适用于未指定命名的@Resource
!
下面的例子有一个customerPreferenceDao
字段,首先要查找一个名叫 “customerPreferenceDao” 的 bean,然后回退为一个原始类型以匹配类型CustomerPreferenceDao
。"context" 字段将基于已知解决的依赖类型ApplicationContext
而被注入。
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
} // ...}
3.2 @Autowired
参考开发文档基于注解(Annotation-based)的配置--->@Autowired
3.3 自己编码实现Spring注解功能注入对象
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface ItcastResource {
public String name() default "";
}
修改MyClassPathXMLApplicationContext.java
public MyClassPathXMLApplicationContext(String filename){
this.readXML(filename);
this.instanceBeans();
this.annotationInject();
this.injectObject();
}
private void annotationInject() {
for(String beanName : sigletons.keySet()){
Object bean = sigletons.get(beanName);
if(bean!=null){
try {
PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
for(PropertyDescriptor properdesc : ps){
Method setter = properdesc.getWriteMethod();
if(setter!=null && setter.isAnnotationPresent(ItcastResource.class)){
ItcastResource resource = setter.getAnnotation(ItcastResource.class);
Object value = null;
if(resource.name()!=null && !"".equals(resource.name())){
value = sigletons.get(resource.name());
}else{
value = sigletons.get(properdesc.getName());
if(value==null){
for(String key : sigletons.keySet()){
if(properdesc.getPropertyType().isAssignableFrom(sigletons.get(key).getClass())){
value = sigletons.get(key);
break;
}
}
}
}
setter.setAccessible(true);
setter.invoke(bean, value);
}
}
Field[] fields = bean.getClass().getDeclaredFields();
for(Field field : fields){
if(field.isAnnotationPresent(ItcastResource.class)){
ItcastResource resource = field.getAnnotation(ItcastResource.class);
Object value = null;
if(resource.name()!=null && !"".equals(resource.name())){
value = sigletons.get(resource.name());
}else{
value = sigletons.get(field.getName());
if(value==null){
for(String key : sigletons.keySet()){
if(field.getType().isAssignableFrom(sigletons.get(key).getClass())){
value = sigletons.get(key);
break;
}
}
}
}
field.setAccessible(true);
field.set(bean, value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.自动装配
5.对受管组件的Classpath自动扫描
<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="cn.itcast"/>
</beans>
PS:Spring 2.5引入了更多典型化注解(stereotype annotations): @Component
、@Service
和 @Controller
。 @Component
是所有受Spring管理组件的通用形式; 而@Repository
、@Service
和 @Controller
则是@Component
的细化, 用来表示更具体的用例(例如,分别对应了持久化层、服务层和表现层)。也就是说, 你能用@Component
来注解你的组件类, 但如果用@Repository
、@Service
或@Controller
来注解它们,你的类也许能更好地被工具处理,或与切面进行关联。 例如,这些典型化注解可以成为理想的切入点目标。当然,在Spring Framework以后的版本中, @Repository
、@Service
和 @Controller
也许还能携带更多语义。如此一来,如果你正在考虑服务层中是该用 @Component
还是@Service
, 那@Service
显然是更好的选择。同样的,就像前面说的那样, @Repository
已经能在持久化层中进行异常转换时被作为标记使用了。
@Component
public class Componet{
}
@Service
public class Service{
}
@Controller
public class Action{
}
@Repository
public class Dao{
}