十、依赖注入之手工装配—注解方式
注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果。
手工装配依赖对象,在这种方式中又有两种编程方式:
1,在xml配置文件中,通过在bean节点下配置,如之前的例子
2,在Java代码中,使用@Autowired或@Resource注解方式进行装配。但我们需要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-2.5.xsd"> <context:annotation-config/> </beans>
这个配置,隐式注册了多个对注释进行解析处理的处理器:
AutowireAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, RequireAnnotationBeanPostProcessoor
@Autowired和@Resource的区别:
@Autowired是Spring(org.springframework.beans.factory.annotation.Autowired)提供的,默认按类型装配;默认情况下它要求依赖对象必须存在,如果运行null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下:
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; public class PersonServiceBean implements PersonService { @Autowired @Qualifier("personDaoxxxx") private PersonDao personDao; //用在字段上 private String name = "xxxx"; public void save() { personDao.add(); System.out.println(name); } }
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.springframework.beans.factory.annotation.Autowired; public class PersonServiceBean implements PersonService { private PersonDao personDao; private String name = "xxxx"; @Autowired public void setPersonDao(PersonDao personDao) { //用于属性的Setter方法上 this.personDao = personDao; } public void save() { personDao.add(); System.out.println(name); } }
@Resource是J2EE(javax.annocation.Resource)提供的(推荐,因为没有和框架紧密耦合),也可以标注在字段或属性的Setter方法上,但它默认按名称装配,当找不到与名称匹配的bean才会按类型装配。当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的Setter方法上,即默认取属性名作为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: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-2.5.xsd"> <context:annotation-config/> <bean id="personDao" class="cn.sjmz.dao.impl.PersonDaoBean"></bean> <bean id="personService" class="cn.sjmz.service.impl.PersonServiceBean"></bean> </beans>
代码示例:
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import javax.annotation.Resource; public class PersonServiceBean implements PersonService { @Resource private PersonDao personDao; private String name = "xxxx"; public void save() { personDao.add(); System.out.println(name); } }
测试代码:
import cn.sjmz.service.PersonService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public void setUp() throws Exception { } @Test public void instanceSpring() { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); PersonService personService = (PersonService) ctx.getBean("personService"); personService.save(); } }
执行OK!
配置文件改为如下(id="personDaoxxxx"),也可正常注入。因为按名称匹配不到,会按类型匹配。
<?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-2.5.xsd"> <context:annotation-config/> <bean id="personDaoxxxx" class="cn.sjmz.dao.impl.PersonDaoBean"></bean> <bean id="personService" class="cn.sjmz.service.impl.PersonServiceBean"></bean> </beans>
注入也可以指定name属性,一旦制定了name属性,就只能按名称装配了。如果name属性值在配置文件中存在,会提示Cannot resolve bean 'personDaoyyy'错误
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import javax.annotation.Resource; public class PersonServiceBean implements PersonService { @Resource(name = "personDaoxxxx") private PersonDao personDao; private String name = "xxxx"; public void save() { personDao.add(); System.out.println(name); } }
注入也可以设定到set方法上
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import javax.annotation.Resource; public class PersonServiceBean implements PersonService { private PersonDao personDao; private String name = "xxxx"; @Resource public void setPersonDao(PersonDao personDao) { this.personDao = personDao; } public void save() { personDao.add(); System.out.println(name); } }
十一、注解方式的实现原理
自定义注解代码:
package cn.sjmz.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //自定义Resource注解 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface MyAnnotation { String name() default ""; }
注解注入实现代码:
import cn.sjmz.annotation.MyAnnotation; import org.apache.commons.beanutils.ConvertUtils; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.XPath; import org.dom4j.io.SAXReader; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; 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; public class ItcastClassPathXmlApplicationContext { private List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>(); private Map<String, Object> singletons = new HashMap<String, Object>(); public ItcastClassPathXmlApplicationContext(String fileName) { this.readXML(fileName); this.instanceBeans(); this.annotationInject(); //注解注入 this.injectObject(); } /** * 注解注入实现 */ private void annotationInject() { //1.循环所有的bean对象 for (String beanName : singletons.keySet()) { Object bean = singletons.get(beanName); //获取bean对象 //判断bean对象是否存在 if (bean != null) { try { //bean对象存在,获取bean对象的属性 PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors(); for (PropertyDescriptor propertyDesc : ps) { Method setter = propertyDesc.getWriteMethod(); //获取属性的setter方法 //setter方法存在,运用反射技术判断setter方法上是否有MyAnnotation注解 if (setter != null && setter.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = setter.getAnnotation(MyAnnotation.class); Object value = null; if (annotation.name() != null && !"".equals(annotation.name())) { value = singletons.containsKey(annotation.name()); } else {//没有设定name属性 value = singletons.get(propertyDesc.getName()); if (value == null) { //按类型寻找 for (String key : singletons.keySet()) { //判断类型是否匹配 if (propertyDesc.getPropertyType().isAssignableFrom(singletons.get(key).getClass())) { value = singletons.get(key); break; } } } } setter.setAccessible(true); setter.invoke(bean, value); //把引用对象注入到属性 } } //获取bean对象的字段 Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { //判断字段上是否存在注解 if (field.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = field.getAnnotation(MyAnnotation.class); Object value = null; if (annotation.name() != null && !"".equals(annotation.name())) { value = singletons.containsKey(annotation.name()); } else { value = singletons.get(field.getName()); if (value == null) { //按类型寻找 for (String key : singletons.keySet()) { //判断类型是否匹配 if (field.getType().isAssignableFrom(singletons.get(key).getClass())) { value = singletons.get(key); break; } } } } field.setAccessible(true); //运行访问private字段 field.set(bean, value); //把引用对象注入到属性 } } } catch (Exception e) { e.printStackTrace(); } } } } /** * 为bean对象的属性注入值 */ private void injectObject() { for (BeanDefinition beanDefinition : beanDefinitions) { Object bean = singletons.get(beanDefinition.getId()); if (bean != null) { try { PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors(); for (PropertyDefinition propertyDefinition : beanDefinition.getPropertyDefinitionList()) { for (PropertyDescriptor propertyDesc : ps) { if (propertyDefinition.getName().equals(propertyDesc.getName())) { Method setter = propertyDesc.getWriteMethod(); //获取属性的setter方法 if (setter != null) { Object value; // ref注入 if (propertyDefinition.getRef() != null && !"".equals(propertyDefinition.getRef().trim())) { value = singletons.get(propertyDefinition.getRef()); } else { //基本类型注入 value = ConvertUtils.convert(propertyDefinition.getValue(), propertyDesc.getPropertyType()); } setter.setAccessible(true); //setter方法是private时,需要增加这条语句 setter.invoke(bean, value); //把引用对象注入到属性 } break; } } } } catch (Exception e) { e.printStackTrace(); } } } } /** * 完成bean的实例化 */ private void instanceBeans() { for (BeanDefinition beanDefinition : beanDefinitions) { if (beanDefinition.getClassName() != null && (!"".equals(beanDefinition.getClassName().trim()))) { try { singletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance()); } catch (Exception e) { e.printStackTrace(); } } } } /** * 读取xml配置文件 * * @param fileName */ public void readXML(String fileName) { SAXReader saxReader = new SAXReader(); Document document = null; try { URL xmlPath = this.getClass().getClassLoader().getResource(fileName); document = saxReader.read(xmlPath); 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"); //创建beans/bean查询路径 xsub.setNamespaceURIs(nsMap); //设置命名空间 List<Element> beans = xsub.selectNodes(document); //获取文档下所有bean节点 for (Element element : beans) { String id = element.attributeValue("id"); //获取id属性值 String clazz = element.attributeValue("class"); //获取class属性值 BeanDefinition beanDefinition = new BeanDefinition(id, clazz); XPath propertySub = element.createXPath("ns:property"); propertySub.setNamespaceURIs(nsMap); //设置命名空间 List<Element> properties = propertySub.selectNodes(element); for (Element property : properties) { String propertyName = property.attributeValue("name"); String propertyRef = property.attributeValue("ref"); String propertyValue = property.attributeValue("value"); PropertyDefinition propertyDefinition = new PropertyDefinition(propertyName, propertyRef, propertyValue); beanDefinition.getPropertyDefinitionList().add(propertyDefinition); } beanDefinitions.add(beanDefinition); } } catch (Exception e) { e.printStackTrace(); } } /** * 获取bean实例 * * @param beanName * @return */ public Object getBean(String beanName) { return this.singletons.get(beanName); } }
配置文件示例:
<?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-2.5.xsd"> <context:annotation-config/> <bean id="personDaoxxxx" class="cn.sjmz.dao.impl.PersonDaoBean"></bean> <bean id="personService" class="cn.sjmz.service.impl.PersonServiceBean"></bean> </beans>
注解使用示例:
package cn.sjmz.service.impl; import cn.sjmz.annotation.MyAnnotation; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; public class PersonServiceBean implements PersonService { @MyAnnotation private PersonDao personDao; private String name = "xxxx"; public void save() { personDao.add(); System.out.println(name); } }
测试代码:
import cn.sjmz.service.PersonService; import org.junit.Test; public class SpringTest { public void setUp() throws Exception { } @Test public void instanceSpring() { // ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); ItcastClassPathXmlApplicationContext ctx = new ItcastClassPathXmlApplicationContext("beans.xml"); PersonService personService = (PersonService) ctx.getBean("personService"); personService.save(); } }
正常执行,符合预期结果!
十二、依赖注入之自动装配
对于自动装配,了解一下就可以,不推荐大家使用。
使用示例: <bean id="..." class="..." autowire="byType">
autowire的属性取值如下:
1)byType:按类型装配,可以根据属性的类型,在容器中寻找跟该类型匹配的bean。如果发现多个,那么将会抛出异常。如果没有找到,即属性值为null。
2) byName:按名称装配,可以根据属性的名称,在容器中寻找跟该属性名相同的bean,如果没有找到,即属性值为null。
3) constructor:与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
4) autodetect:通过bean的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式。
自动装配配置文件示例:
<?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-2.5.xsd"> <context:annotation-config/> <bean id="personDao" class="cn.sjmz.dao.impl.PersonDaoBean"></bean> <bean id="personService" class="cn.sjmz.service.impl.PersonServiceBean" autowire="byType"></bean> </beans>
代码示例:(注意:需要有Set方法,否则会报空指针异常)
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; public class PersonServiceBean implements PersonService { private PersonDao personDao; private String name = "xxxx"; public void setPersonDao(PersonDao personDao) { this.personDao = personDao; } public void save() { personDao.add(); System.out.println(name); } }
测试代码示例:
import cn.sjmz.service.PersonService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public void setUp() throws Exception { } @Test public void instanceSpring() { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); PersonService personService = (PersonService) ctx.getBean("personService"); personService.save(); } }
测试结构OK!
十三、通过在classpath自动扫描的方式把组件纳入spring容器中管理
前面的例子我们都是使用XML的bean定义来配置组件。在稍大的项目中,通常会有上百个组件,如果这些组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找及维护起来也不方便。Spring2.5为我们引入了组件自动扫描机制。他可以在类路径底下寻找标注了@Component、@service、@Controller、@Repository注解的类,并把这些类纳入进spring容器中管理。他的作用和在xml文件中使用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: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-2.5.xsd"> <context:component-scan base-package="cn.sjmz"/> </beans>
其中base-package为需要扫描的包(含子包),另外有了<context:component-scan>的配置,就可以不用配置<context:annotation-config>了,因为前者已经包含了后者注册的所有注解处理器。
@Service 用于标注业务层组件
@Controller 用于标注控制层组件(如Struts中的action)
@Repository 用于标注数据访问组件,即DAO组件
@Component 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
实现代码示例:
package cn.sjmz.dao.impl; import cn.sjmz.dao.PersonDao; import org.springframework.stereotype.Repository; @Repository public class PersonDaoBean implements PersonDao { public void add(){ System.out.println("执行PersonDaoBean中的add()方法"); } }
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.springframework.stereotype.Service; @Service public class PersonServiceBean implements PersonService { private PersonDao personDao; private String name = "xxxx"; public void setPersonDao(PersonDao personDao) { this.personDao = personDao; } public void save() { personDao.add(); System.out.println(name); } }
测试代码示例:(注:默认getBean的名称为首字母小写的bean全名)
import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public void setUp() throws Exception { } @Test public void instanceSpring() { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); PersonService personService = (PersonService) ctx.getBean("personServiceBean"); PersonDao personDao = (PersonDao) ctx.getBean("personDaoBean"); System.out.println(personService); System.out.println(personDao); } }
输出结果:
cn.sjmz.service.impl.PersonServiceBean@255b53dc
cn.sjmz.dao.impl.PersonDaoBean@1dd92fe2
也可以更改getBean使用的bean名称,如下(更改getBean使用名称为personService):
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.springframework.stereotype.Service; @Service("personService") public class PersonServiceBean implements PersonService { private PersonDao personDao; private String name = "xxxx"; public void setPersonDao(PersonDao personDao) { this.personDao = personDao; } public void save() { personDao.add(); System.out.println(name); } }
测试代码:
import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public void setUp() throws Exception { } @Test public void instanceSpring() { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); PersonService personService = (PersonService) ctx.getBean("personService"); PersonDao personDao = (PersonDao) ctx.getBean("personDaoBean"); System.out.println(personService); System.out.println(personDao); } }
测试结果(OK):
cn.sjmz.service.impl.PersonServiceBean@255b53dc
cn.sjmz.dao.impl.PersonDaoBean@1dd92fe2
通过@Scope修改bean的作用域范围,默认为singleton,即getBean多次返回的实例为相同实例。如果改为@Scope("prototype"),则每次实例化返回为不同实例。
另外,还可以通过@PostConstruct注解指定初始化方法、通过@PreDestory指定销毁方法
代码示例:
package cn.sjmz.service.impl; import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Service("personService") @Scope("prototype") public class PersonServiceBean implements PersonService { private PersonDao personDao; private String name = "xxxx"; // public void setPersonDao(PersonDao personDao) { // this.personDao = personDao; // } @PostConstruct public void initMethod() { System.out.println("PersonServiceBean中的initMethod."); } @PreDestroy public void destroyMethod() { System.out.println("PersonServiceBean中的destroyMethod."); } public void save() { personDao.add(); System.out.println(name); } }
测试代码:
import cn.sjmz.dao.PersonDao; import cn.sjmz.service.PersonService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public void setUp() throws Exception { } @Test public void instanceSpring() { // ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); PersonService personService = (PersonService) ctx.getBean("personService"); PersonService personService2 = (PersonService) ctx.getBean("personService"); PersonDao personDao = (PersonDao) ctx.getBean("personDaoBean"); System.out.println(personService); System.out.println(personService2); System.out.println(personDao); ctx.close(); //关闭容器 } }
输出结果:(结果不符合预期,发现destroyMethod没有执行。 测试发现不设置@Scope("prototype")输出结果符合预期,destroyMethod可以正常打印,根本原因待查!!!)
PersonServiceBean中的initMethod.
PersonServiceBean中的initMethod.
cn.sjmz.service.impl.PersonServiceBean@62e136d3
cn.sjmz.service.impl.PersonServiceBean@c8e4bb0
cn.sjmz.dao.impl.PersonDaoBean@6279cee3