Spring 编码剖析@Resource注解的实现原理

Spring 编码剖析@Resource注解的实现原理
June 15, 2011 | tags 传智播客Spring2.5观看笔记   | views 619
Comments 1
下面解剖一下Spring内部是如何实现@Resource注解的,现在从头到尾通过传智播客版的Spring微量容器让它实现通过注解方式,来进行依赖对象的注入

先建一个传智播客的Resource注解 ItcastResource.java
package junit.test; 
 
import java.lang.annotation.ElementType; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 
 
@Retention(RetentionPolicy.RUNTIME) 
/* 
    指定一下注解保留的范围,注解的信息是保留在源代码上面呢?还是只保留在源代码和编译过后的class文件中? 
    还是保留在运行期?如果保留在运行期那么源代码也会存在注解,编译过后的class文件也会存在注解,运行期 
    也会把注解加载进来 
    这里选择运行期,因为在运行期我们需要得到注解的信息 
*/ 
@Target( { ElementType.FIELD, ElementType.METHOD }) 
/* 
    注解可以标注在什么地方?有些可以标注在属性上面,有些可以标注在字段上面,有些可以标注在类型上面 
*/ 
public @interface ItcastResource { 
    public String name() default ""; 
}


然后将该注解加入PersonServiceBean里
PersonServiceBean.java
package cn.itcast.service.impl; 
 
import junit.test.ItcastResource; 
 
import cn.itcast.dao.PersonDao; 
import cn.itcast.service.PersonService; 
 
public class PersonServiceBean implements PersonService { 
    private PersonDao personDao; 
    private String name; 
 
    @ItcastResource 
    public void setPersonDao(PersonDao personDao) { 
        this.personDao = personDao; 
    } 
 
    public PersonServiceBean(){} 
 
    public PersonServiceBean(PersonDao personDao, String name) { 
        this.personDao = personDao; 
        this.name = name; 
    } 
 
    public void save(){ 
        //System.out.println(name); 
        personDao.add(); 
    } 
}



注意了,@ItcastResource这个注解能够把beans.xml里的personDaoxxxx对象注入到属性里面吗?是不能的。通过看@ItcastResource的源代码,它什么也没做,如果我们要用注解把依赖对象注入到属性上面呢,我们后面是不是应该有个控制器
对它进行处理啊?否则它是干不了活的。 所以在这里要通过一个处理器对它进行处理,在这里我们这样做。。首先解析PersonServiceBean这个类里面的所有属性,在属性的set方法上面是否标注了@ItcastResource这个注解,如果标注了这个注解,我们就判断它是否有name属性,如果没配name属性,我们就取属性的名称,到 Spring容器里寻找这个bean;如果没寻找到呢,我们再根据属性的类型去Spring容器寻找跟这个类型匹配的bean,然后再把它给注入进来,过程挺复杂的,通过代码一句一句敲,就好理解了

ItcastClassPathXMLApplicationContext.java
package junit.test; 
 
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容器 
 */ 
public class ItcastClassPathXMLApplicationContext { 
    private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>(); 
    private Map<String, Object> sigletons = new HashMap<String, Object>(); 
 
    public ItcastClassPathXMLApplicationContext(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){//获取到bean对象后就判断下bean对象是否存在 
                try { 
                    //对属性进行处理 
                    PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors(); 
                    //获取bean的属性 
                    for(PropertyDescriptor properdesc : ps){//for循环属性描述 
                        Method setter = properdesc.getWriteMethod();//获取属性的setter方法 
                        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){//当找不到与名称匹配的bean会按类型去寻找 
                                    for(String key : sigletons.keySet()){//for循环所有的bean对象 
                                        if(properdesc.getPropertyType().isAssignableFrom(sigletons.get(key).getClass())){ 
                                            //判断属性的类型是否和bean想匹配 
                                            //isAssignableFrom这个方法判断properdesc.getPropertyType()这个类型是否是 
                                            //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);//设置允许访问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(); 
                    //Introspector通过这个类可以取得bean的定义信息 
                    for(PropertyDefinition propertyDefinition : beanDefinition.getPropertys()){ 
                        for(PropertyDescriptor properdesc : ps){ 
                            if(propertyDefinition.getName().equals(properdesc.getName())){ 
                                Method setter = properdesc.getWriteMethod();// 获取属性的setter方法,private 
                                if(setter!=null){//属性可能没有set方法,所以这里要判断一下 
                                    Object value = null; 
                                    if(propertyDefinition.getRef()!=null && !"".equals(propertyDefinition.getRef().trim())){ 
                                        value = sigletons.get(propertyDefinition.getRef()); 
                                    }else{ 
                                        value = ConvertUtils.convert(propertyDefinition.getValue(), properdesc.getPropertyType()); 
                                        /* 
                                         我们怎么将本身是字符串的值转成相应的属性类型的值呢? 
                                         这时我们可以使用一个叫beanutils的工具,是Apache开源组织给我们提供的,使用它的话,可以很容易的 
                                         把字符串类型的值转换成我需要的属性类型的值 
                                        */ 
                                    } 
                                    setter.setAccessible(true);//如果set方法是私有的话,要设置它允许被访问 
                                    setter.invoke(bean, value);// 把引用对象注入到属性 
                                } 
                                break; 
                            } 
                        } 
                    } 
                } catch (Exception e) { 
                } 
            } 
        } 
    } 
    /** 
     * 实现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) { 
                // 通过反射技术把bean都创建出来 
                e.printStackTrace(); 
            } 
        } 
 
    } 
    /** 
     * 读取xml配置文件 
     */ 
    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实例 
     */ 
    public Object getBean(String beanName){ 
        return this.sigletons.get(beanName); 
    } 
}


接下来是测试 SpringTest.java
package junit.test; 
 
import org.junit.BeforeClass; 
import org.junit.Test; 
 
import cn.itcast.service.PersonService; 
 
public class SpringTest { 
 
    @BeforeClass 
    public static void setUpBeforeClass() throws Exception { 
    } 
 
    @Test public void instanceSpring(){ 
        ItcastClassPathXMLApplicationContext ctx = new ItcastClassPathXMLApplicationContext("beans.xml"); 
        PersonService personService = (PersonService)ctx.getBean("personService"); 
        personService.save(); 
        //ctx.close(); 
    } 
}


我们就来观察一下,它能否帮我们采用注解@ItcastResource把依赖对象注入到属性中去,如果注入成功的话就能正确打出一句话,如果注入不成功就会有空指针异常
运行单元测试代码,控制台输出:执行PersonDaoBean里的add()方法
证明我们注入成功了。
由此可见,注解到底是怎么的工作原理呢?在这里已经给大家充分展示出来了, 柚子舍CC霜注解本身干不了任何事情,它和XML文件一样,只起到配置的作用,注解代表某个业务意义(好比刚才的加入@ItcastResource注解就能注入对象,但是注解它背后可以有个处理器,这里的处理器代码里到底怎么做呢?就和annotationInject方法里的一样了,首先解析所有属性,判断属性上面是否存在这个注解,如果存在这个注解,再根据搜索规则来取得这个bean,然后通过反射技术注入进去;如果注入到字段上面呢?也一样....)
在PersonServiceBean.java里,也可以把@ItcastResource加到字段上面去,比如@ItcastResource private PersonDao personDao;
之后运行单元测试代码,控制台输出"执行PersonDaoBean里的add()方法",字段方式注入成功。

现在我们对Spring的一些采用注解方式注入依赖对象的原理进行了一个更深层次的解剖,相信以后大家在理解起来这个注解是怎么工作的,应该是很清晰的了
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值