Spring注解原理的詳細剖析與實現


本文主要分為三部分:

一、注解的基本概念和原理及其簡單實用

二、Spring中如何使用注解

三、編碼剖析spring@Resource的實現原理

本文轉自:http://freewxy.iteye.com/blog/1149128/

 


 

一、注解的基本概念和原理及其簡單實用

注解(Annotation) 提供了一種安全的類似注釋的機制,為我們在代碼中添加信息提供了一種形式化得方法,使我們可以在稍后某個時刻方便的使用這些數據(通過解析注解來使用這些 數據),用來將任何的信息或者元數據與程序元素(類、方法、成員變量等)進行關聯。其實就是更加直觀更加明了的說明,這些說明信息與程序業務邏輯沒有關 系,並且是供指定的工具或框架使用的。Annotation像一種修飾符一樣,應用於包、類型、構造方法、方法、成員變量、參數及本地變量的申明語句中。

Annotation其實是一種接口。通過java的反射機制相關的API來訪問Annotation信息。相關類(框架或工具中的類)根據這些信息來決定如何使用該程序元素或改變它們的行為。Java語言解釋器在工作時會忽略這些Annotation,因此在JVM中這些Annotation是“不起作用”的,只能通過配套的工具才能對這些Annotation類型的信息進行訪問和處理。

Annotation和interface的異同:

1、 annotition的類型使用關鍵字@interface而不是interface。它繼承了java.lang.annotition.Annotition接口,並非申明了一個interface。

2、 Annotation類型、方法定義是獨特的、受限制的。Annotation類型的方法必須申明為無參數、無異常拋出的。這些方法定義了Annotation的成員:方法名稱為了成員名,而方法返回值稱為了成員的類型。而方法返回值必須為primitive類型、Class類型、枚舉類型、Annotation類型或者由前面類型之一作為元素的一位數組。方法的后面可以使用default和一個默認數值來申明成員的默認值,null不能作為成員的默認值,這與我們在非Annotation類型中定義方法有很大不同。Annotation類型和他的方法不能使用Annotation類型的參數,成員不能是generic。只有返回值類型是Class的方法可以在Annotation類型中使用generic,因為此方法能夠用類轉換將各種類型轉換為Class。

3、 Annotation類型又與接口有着近似之處。它們可以定義常量、靜態成員類型(比如枚舉類型定義)。Annotation類型也可以如接口一般被實現或者繼承。

 

*元注解@Target,@Retention,@Documented,@Inherited 

* @Target 表示該注解用於什么地方,可能的 ElemenetType 參數包括: 
* ElemenetType.CONSTRUCTOR 構造器聲明 
* ElemenetType.FIELD 域聲明(包括 enum 實例) 
* ElemenetType.LOCAL_VARIABLE 局部變量聲明 
* ElemenetType.METHOD 方法聲明 
* ElemenetType.PACKAGE 包聲明 
* ElemenetType.PARAMETER 參數聲明 
* ElemenetType.TYPE 類,接口(包括注解類型)或enum聲明 

* @Retention 表示在什么級別保存該注解信息。可選的 RetentionPolicy 參數包括: 
* RetentionPolicy.SOURCE 注解將被編譯器丟棄 
* RetentionPolicy.CLASS 注解在class文件中可用,但會被VM丟棄 
* RetentionPolicy.RUNTIME VM將在運行期也保留注釋,因此可以通過反射機制讀取注解的信息。 

* @Documented 將此注解包含在 javadoc 中 

* @Inherited 允許子類繼承父類中的注解

 

使用:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

 

 

舉例:

package com.wxy.annotation;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; 
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**   
*  定義注解Test
*  注解中含有兩個元素 id 和 description
*  description元素有默認值"no description"  
*   @create-time     2011-8-12   下午02:22:28   
*   @revision          $Id
*/
 
//該注解用於方法聲明
@Target(ElementType.METHOD)
//VM將在運行期也保留注釋,因此可以通過反射機制讀取注解的信息
@Retention(RetentionPolicy.RUNTIME)
//將此注解包含在javadoc中
@Documented
//允許子類繼承父類中的注解
@Inherited
public @interface Test { 
    public int id();
    public String description() default "no description";
}
package com.wxy.annotation;
import java.lang.reflect.Method;
/**
*   使用注解和解析注解
*  
*   @creator            xiaoyu.wang   
*   @create-time     2011-8-12   下午03:49:17   
*   @revision          $Id
*/
public class Test_1{
    /**
     * 被注釋的三個方法
     */
    @Test(id = 1, description = "hello method1")
    public void method1() {
    }
    @Test(id = 2)
    public void method2() {
    }
    @Test(id = 3, description = "last method3")
    /**
     * 解析注釋,將Test_1類所有被注解方法的信息打印出來
     * @param args
     */
    public static void main(String[] args) {
        Method[] methods = Test_1.class.getDeclaredMethods();
        for (Method method : methods) {
            //判斷方法中是否有指定注解類型的注解
            boolean hasAnnotation = method.isAnnotationPresent(Test.class);
            if (hasAnnotation) {
                //根據注解類型返回方法的指定類型注解
                Test annotation = method.getAnnotation(Test.class);
                System.out.println("Test(method=" + method.getName() + ",id=" + annotation.id()
                                   + ",description=" + annotation.description() + ")");
            }
        }
    }
}

 

二、spring中注解@Resource的使用

1、修改bean.xml,引入命名空間(保證項目能引用到common-annotations.jar)

?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="peopleDao" class="com.wxy.dao.impl.PeopleDaoBean"></bean>
         <bean id="peopleService" class="com.wxy.service.impl.PeopleServiceBean"       
         </bean>
</beans> 

 

2、在PeopleServiceBean中使用@Resource注解

public class PeopleServiceBean implements PeopleService {
    @Resource
    private PeopleDao peopleDao;
    private String    name = "wxy";
    private Integer   id   = 1;
    …
}

 

3、 測試

public class Test {
    public static void main(String[] args) {
        //IOC容器實例化
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        PeopleService peopleService = (PeopleService) ac.getBean("peopleService");
        peopleService.save();
    }
}

 

4、 結果

--> the method is called save()! name=wxy,id=1
this is the method PeopleDaoBean.add()!

 

也可以在setter方法上使用@Resource:

public class PeopleServiceBean implements PeopleService {
    private PeopleDao peopleDao;
    private String    name = "wxy";
    private Integer   id   = 1;
    /** 
     * @return the peopleDao
     */
    public PeopleDao getPeopleDao() {
        return peopleDao;
    }
 
    /**
     * @param peopleDao the peopleDao to set
     */
    @Resource
    public void setPeopleDao(PeopleDao peopleDao) {
        this.peopleDao = peopleDao;
    }
     …..
}

結果一樣。

 

三、@Resource注解的實現原理(只用於配置,不用於干活)

1、新建Annotation類型文件

 package com.wxy.annotation;
 
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 WxyResource {
    String name() default "";
}

 

2、使用自定義的注解@WxyResource

public class PeopleServiceBean implements PeopleService {
    private PeopleDao peopleDao;
    private String    name = "wxy";
    private Integer   id   = 1;
    /** 
     * @param peopleDao the peopleDao to set
     */
    @WxyResource
    public void setPeopleDao(PeopleDao peopleDao) {
        this.peopleDao = peopleDao;
    }
    …
}

 

3、事實上上一步沒有實現,這一步開始實現注入原理,在自定義spring容器中添加注解功能

public class WxyClassPathXMLApplicationContext {
 
    //存放BeanDefinition的列表,在beans.xml中定義的bean可能不止一個
    private final List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();
    //將類名作為索引,將創建的Bean對象存入到Map中
    private final Map<String, Object>  sigletons   = new HashMap<String, Object>();
    public WxyClassPathXMLApplicationContext(String fileName) {
        //讀取xml配置文件
        this.readXML(fileName);
        //實例化bean
        this.instanceBeans();
        //處理注解方式
        this.annotationInject();
        //注入對象
        this.injectObject();
    }
    /** 
     * 使用注解方式注入對象方法實現
     * @throws IntrospectionException 
     */
    private void annotationInject() {
        //循環所有bean對象
        for (String beanName : sigletons.keySet()) {
            //獲取bean對象
            Object bean = sigletons.get(beanName);
            //如果bean不為空,取得bean的屬性
            if (bean != null) {
                try {
                    //按屬性注入
                    PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass())
                        .getPropertyDescriptors();
                    for (PropertyDescriptor properdesc : ps) {
                        //獲取屬性的setter方法
                        Method setter = properdesc.getWriteMethod();
                        //判斷注解是否存在
                        if (setter != null && setter.isAnnotationPresent(WxyResource.class)) {
                            //取得注解
                            WxyResource resource = setter.getAnnotation(WxyResource.class);
                            Object value = null;
                            //如果按名字找到
                            if (resource.name() != null && !"".equals(resource.name())) {
                                //取得容器中的bean對象
                                value = sigletons.get(resource.name());
 
                            } else {//沒有按名字找到,按類型尋找
                                //取得容器中的bean對象
                                value = sigletons.get(resource.name());
                                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(WxyResource.class)) {
                            //取得注解
                            WxyResource resource = field.getAnnotation(WxyResource.class);
                            Object value = null;
                            //如果按名字找到
                            if (resource.name() != null && !"".equals(resource.name())) {
                                //取得容器中的bean對象
                                value = sigletons.get(resource.name());
                            } else {//沒有按名字找到,按類型尋找
                                //取得容器中的bean對象
                                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;
                                        }
                                    }
                                }
                            }
                            //允許訪問private 
                            field.setAccessible(true);
                            field.set(bean, value);
                        }
                    }
                } catch (IntrospectionException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    /** 
     *為bean對象的屬性注入值
     */
  private void injectObject() {
    ….
  }
  ….
}

 

4、 使用自定義容器測試

public class MyTest { 
    /**
     * @param args
     */
    public static void main(String[] args) {
        //MyIOC容器實例化
        WxyClassPathXMLApplicationContext ac = new WxyClassPathXMLApplicationContext("beans.xml");
        PeopleService peopleService = (PeopleService) ac.getBean("peopleService");
        peopleService.save();
    }
}

 

5、 測試結果

--> the method is called save()! name=wxy,id=1
this is the method PeopleDaoBean.add()!

 

注解本身不做任何事情,只是像xml文件一樣起到配置作用。注解代表的是某種業務意義,注解背后處理器的工作原理如上源碼實現:首先解析所有屬性,判斷屬性上是否存在指定注解,如果存在則根據搜索規則取得bean,然后利用反射原理注入。如果標注在字段上面,也可以通過字段的反射技術取得注解,根據搜索規則取得bean,然后利用反射技術注入。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM