深入探索spring技术内幕(四): 剖析@Resource注解实现原理与注解注入

一、@Resource注解原理

@Resource可以标注在字段或属性的setter方法上

1.  如果指定了name属性, 那么就按name属性的名称装配; 

2. 如果没有指定name属性, 那就按照默认的名称查找依赖对象;

3. 如果按默认名称查找不到依赖对象, 那么@Resource注解就会回退到按类型装配;


① 先写一个自己的@MyResource:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Retention(RetentionPolicy.RUNTIME) // 指定注解保留的范围 (运行期)
@Target({ ElementType.FIELD, ElementType.METHOD }) // 允许注解标注的位置 (属性, 方法)
public @interface MyResource {
	public String name() default ""; // 提供name属性
}
② Spring Bean Factory: ClassPathXMLApplicationContext

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;
import org.apache.commons.beanutils.ConvertUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

/**
 * Spring Bean Factory
 */
public class ClassPathXMLApplicationContext {
	private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();
	private Map<String, Object> sigletons = new HashMap<String, Object>();

	public ClassPathXMLApplicationContext(String filename) {
		this.readXML(filename);
		this.instanceBeans();
		this.annotationInject();
		this.injectObject();
	}

	/**
	 * 通过注解实现注入依赖对象
	 */
	private void annotationInject() {
		for (String beanName : sigletons.keySet()) { // 循环所有的Bean对象
			Object bean = sigletons.get(beanName);
			if (bean != null) {
				try {
					// 查找属性的setter上是否有注解
					PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors(); 
					for (PropertyDescriptor properdesc : ps) { // 循环所有属性
						Method setter = properdesc.getWriteMethod();// 获取属性的setter方法
						if (setter != null && setter.isAnnotationPresent(MyResource.class)) { // 判断MyResource注解是否存在
							MyResource resource = setter.getAnnotation(MyResource.class);
							Object injectBean = null;
							if (resource.name() != null && !"".equals(resource.name())) {
								injectBean = sigletons.get(resource.name()); // 通过MyResource注解的name属性获取Bean
							} else { 
								injectBean = sigletons.get(properdesc.getName());
								if (injectBean == null) { // 没有指定name属性, 根据属性名称进行寻找
									for (String key : sigletons.keySet()) {
										// 根据属性类型进行寻找
										if (properdesc.getPropertyType().isAssignableFrom(sigletons.get(key).getClass())) { 
											injectBean = sigletons.get(key);
											break;
										}
									}
								}
							}
							setter.setAccessible(true); 
							setter.invoke(bean, injectBean);// 把引用对象注入到属性
						}
					}
					
					// 查找字段上是否有注解
					Field[] fields = bean.getClass().getDeclaredFields(); // 取得声明的所有字段
					for (Field field : fields) {
						if (field.isAnnotationPresent(MyResource.class)) { // 判断字段上是否存在MyResource注解
							MyResource resource = field.getAnnotation(MyResource.class);
							Object value = null;
							if (resource.name() != null && !"".equals(resource.name())) { // 判断是否指定name属性
								value = sigletons.get(resource.name());
							} else {
								value = sigletons.get(field.getName()); // 没有指定name属性,那么根据字段名称寻找
								if (value == null) {
									for (String key : sigletons.keySet()) {
										// 根据字段类型进行寻找
										if (field.getType().isAssignableFrom(sigletons.get(key).getClass())) { 
											value = sigletons.get(key);
											break;
										}
									}
								}
							}
							field.setAccessible(true);// 允许访问private字段
							field.set(bean, value);
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 为bean对象的属性注入值
	 */
	private void injectObject() {
		for (BeanDefinition beanDefinition : beanDefines) {
			Object bean = sigletons.get(beanDefinition.getId());
			if (bean != null) {
				try {
					PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
					for (PropertyDefinition propertyDefinition : beanDefinition.getPropertys()) {
						for (PropertyDescriptor properdesc : ps) {
							if (propertyDefinition.getName().equals(properdesc.getName())) {
								Method setter = properdesc.getWriteMethod(); // 获取属性的setter方法
								if (setter != null) {
									Object injectBean = null;
									if (propertyDefinition.getRef() != null && !"".equals(propertyDefinition.getRef().trim())) {
										injectBean = sigletons.get(propertyDefinition.getRef());
									} else {
										injectBean = ConvertUtils.convert(propertyDefinition.getValue(), properdesc.getPropertyType());
									}
									setter.setAccessible(true); // private method
									setter.invoke(bean, injectBean); // 把引用对象注入到属性
								}
								break;
							}
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 完成bean的实例化
	 */
	private void instanceBeans() {
		for (BeanDefinition beanDefinition : beanDefines) {
			try {
				if (beanDefinition.getClassName() != null && !"".equals(beanDefinition.getClassName().trim()))
					sigletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 * 读取xml配置文件
	 * 
	 * @param filename
	 */
	private 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 beanDefine = new BeanDefinition(id, clazz);
				XPath propertysub = element.createXPath("ns:property");
				propertysub.setNamespaceURIs(nsMap);// 设置命名空间
				List<Element> propertys = propertysub.selectNodes(element);
				for (Element property : propertys) {
					String propertyName = property.attributeValue("name");
					String propertyRef = property.attributeValue("ref");
					String propertyValue = property.attributeValue("value");
					PropertyDefinition propertyDefinition = new PropertyDefinition(propertyName, propertyRef, propertyValue);
					beanDefine.getPropertys().add(propertyDefinition);
				}
				beanDefines.add(beanDefine);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 获取bean实例
	 * 
	 * @param beanName
	 * @return
	 */
	public Object getBean(String beanName) {
		return this.sigletons.get(beanName);
	}
}

③ beans.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-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="personDao" class="com.zdp.dao.impl.PersonDaoImpl" />
	<bean id="personService" class="com.zdp.service.impl.PersonServiceImpl" />
</beans>
④ PersonServiceImpl

import com.zdp.dao.PersonDao;
import com.zdp.myspring.MyResource;
import com.zdp.service.PersonService;

public class PersonServiceImpl implements PersonService {
	private PersonDao personDao;
	
	@MyResource(name="personDao") 
	public void setPersonDao(PersonDao personDao) {
		this.personDao = personDao;
	}

	public void save() {
		personDao.save();
	}
}
⑤ 测试一下

import org.junit.Test;
import com.zdp.service.PersonService;
import com.zdp.myspring.ClassPathXMLApplicationContext;
public class PersonServiceImplTest {
	@Test
	public void testSave() {
		ClassPathXMLApplicationContext ctx = new ClassPathXMLApplicationContext("beans.xml");
		PersonService personService = (PersonService)ctx.getBean("personService");
		personService.save();
	}
}

二、spring注解注入

① 引入common-annotations.jar

② 在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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">
	<context:annotation-config />
</beans>

③ 在Java代码中使用@Autowired或@Resource注解方式进行装配

二者区别: @Autowired默认按类型装配, @Resource默认按名称装配, 当找不到与名称匹配的Bean才会按类型匹配.

@Resource // 配置在属性上
private PersonDao personDao;

@Resource(name="personDao") // 名称通过@Resource的name属性指定
private PersonDao personDao;

@Resource // 配置在setter方法上
public void setPersonDao(PersonDao personDao) {
	this.personDao = personDao; 
}


@Autowired注解是按类型装配依赖对象, 默认情况下它要求依赖对象必须存在, 

如果允许null值, 可以设置required=false

如果想使用按名称装配, 可以结合@Qualifier注解一起使用

@Autowired @Qualifier("personDao")
private PersonDao personDao


三、spring自动扫描和管理Bean

前面的例子都是使用xml的bean定义来配置组件, 在一个稍大的项目中, 通常会有上百个组件, 如果这些组件都采用xml的bean定义来配置, 显然会增加配置文件的体积, 查找及维护起来也不太方便. 


spring2.5为我们引入了组件自动扫描机制, 它可以在类路径下寻找标注了@Component、@Controller、@Service、@Reponsitory注解的类, 并把这些类纳入进spring容器中管理. 它的作用和在xml中使用bean节点配置组件是一样的.

① beans.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-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="com.zdp"/> 
	<!-- base-package为需要扫描的包(含子包) -->
</beans>
② PersonServiceImpl

@Service("personService") 
@Scope("singleton")
public class PersonServiceImpl implements PersonService {
	private PersonDao personDao;
	
	@Resource(name="personDao") 
	public void setPersonDao(PersonDao personDao) {
		this.personDao = personDao;
	}
	
	@PostConstruct
	public void init(){
		System.out.println("init..");
	}
	
	@PreDestroy
	public void destory(){
		System.out.println("destory..");
	}
	public void save() {
		personDao.save();
	}
}
@Controller通常用于标注控制层组件(如struts中的action);

@Service通常用于标注业务层组件;

@Repository通常用于标注数据访问组件, 即DAO组件;

@Component泛指组件, 当组件不好归类的时候, 我们可以使用这个注解进行标注;


你可能感兴趣的:(深入探索spring技术内幕(四): 剖析@Resource注解实现原理与注解注入)