註解學習(模仿springMvc的註解注入方式)

最近在看springMvc的源碼,看到了該框架的注入註解的部分覺的有點吃力,可能仍是對註解的方面的知識還認識的不夠深入,因此特地去學習註解方面的知識。因爲本人也是抱着學習的態度來閱讀源碼,若文章在表述和代碼方面若有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!但願能互相學習。java

1,首先定義三個經常使用的註解Service,Autowired,Contrller;(主要的解釋都在代碼中有,在這裏就很少陳述)web

Service:spring

package com.lishun.Annotation;

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

/*Description:
 * @Target:指定註解的使用範圍(指的是,在哪些類型可使用該註解:Service註解只能在類,接口(包括註解類型)或enum等使用)
 * 可選值:
 * 可選的值在枚舉類 ElemenetType 中,包括: 
          ElemenetType.CONSTRUCTOR 構造器聲明 
          ElemenetType.FIELD 域聲明(包括 enum 實例) 
          ElemenetType.LOCAL_VARIABLE 局部變量聲明
          ElemenetType.ANNOTATION_TYPE 做用於註解量聲明
          ElemenetType.METHOD 方法聲明
          ElemenetType.PACKAGE 包聲明 
          ElemenetType.PARAMETER 參數聲明 
          ElemenetType.TYPE 類,接口(包括註解類型)或enum聲明 

 * */

@Target(ElementType.TYPE)
/*Description:
 * @Retention :表示在什麼級別保存該註解信息
 * 可選的參數值在枚舉類型 RetentionPolicy 中,包括: 
          RetentionPolicy.SOURCE 註解將被編譯器丟棄 
          RetentionPolicy.CLASS 註解在class文件中可用,但會被VM丟棄 
          RetentionPolicy.RUNTIME VM將在運行期也保留註釋,所以能夠經過反射機制讀取註解的信息。 
 * */
@Retention(RetentionPolicy.RUNTIME)

/*@Documented 將此註解包含在 javadoc 中 ,它表明着此註解會被javadoc工具提取成文檔。
 * 在doc文檔中的內容會由於此註解的信息內容不一樣而不一樣。至關與@see,@param 等。
 * */
@Documented


public @interface Service {
    /* @interface用來聲明一個註解,其中的每個方法其實是聲明瞭一個配置參數。
     * 方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。
     * 能夠經過default來聲明參數的默認值。
    */
    String value() default "this is service annotation";
}

 

Autowired:數據庫

package com.lishun.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;

@Target({ElementType.METHOD,ElementType.FIELD})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface Autowired {
      public String value() default "no description";  
}

Contrller:設計模式

package com.lishun.Annotation;

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


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Contrller {
    String value() default "this is contrller annotation";
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------網絡

2:javaBean數據池-BeanFactory:主要存放含有註解的類;框架

package com.lishun.factory;

import java.util.HashMap;
import java.util.Map;
/**
 * Description:存放全部bean的數據池 
 * @author lishun
 * @since 2015-09-10
 */
public class BeanFactory {
    private static Map<String, Object> map = new HashMap<String, Object>();

    public static void addBean(String beanName, Object bean) {
        map.put(beanName, bean);
    }

    public static Object getBean(String beanName) throws Exception {
        Object o = map.get(beanName);
        if (o != null) {
            return o;
        } else {
            throw new Exception("未注入的類型:" + beanName);
        }
    }
    public static Boolean containsBean(String beanName){
        return map.containsKey(beanName);
    }
}

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ide

3:編寫處理註解的核心代碼(這裏涉及的主要知識是反射,若是反射知識不夠熟練的話建議先學習反射方面的知識),主要涉及的兩個類是註解驅動(AnnotationDriven)和註解掃描類(PackUtils-這個類主要的是掃描包名下全部的類(如com.lishun,就是掃描該包下全部的類),代碼主要是來自網絡)工具

AnnotationDriven:學習

package com.lishun.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;

import com.lishun.Annotation.Autowired;
import com.lishun.Annotation.Contrller;
import com.lishun.Annotation.Service;
import com.lishun.factory.BeanFactory;

/**
 * Description:注入驅動類,全部的註解注入都在這裏實現(這裏只實現了經過類型來注入值,其餘方式沒實現,其實代碼都是差很少了,有興趣的能夠自行腦補)
 * @author lishun
 *
 */
public class AnnotationDriven {
    public static void annotationDriven(String packName) throws Exception {
        //注入Service和Contrller
        List<Class<?>> classSaveServicePaths = PackUtils
                .getClassListByAnnotation(packName, Service.class);
        List<Class<?>> classSaveContrllerPaths = PackUtils
                .getClassListByAnnotation(packName, Contrller.class);
        saveBean(classSaveServicePaths);
        saveBean(classSaveContrllerPaths);
        //注入Autowired
        List<Class<?>> classInjectPaths = PackUtils.getClassListByAnnotation(
                packName, Autowired.class);
        inject(classInjectPaths);
    }

    private static void saveBean(List<Class<?>> classSavePaths)
            throws InstantiationException, IllegalAccessException {
        for (Class<?> classPath : classSavePaths) {
            try {
                Class c = Class.forName(classPath.getName());
                Object o = c.newInstance();
                //掃描的到的含有註解的類實例化後保存在池中
                BeanFactory.addBean(classPath.getName(), o);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private static void inject(List<Class<?>> classInjectPaths) throws Exception {
        Object o = null;
        for (Class<?> classInjectPath : classInjectPaths) {

            Class c = Class.forName(classInjectPath.getName());
            //判斷存放bean的池中是否存在該bean
            if (BeanFactory.containsBean(classInjectPath.getName())) {
                o = BeanFactory.getBean(classInjectPath.getName());
            } else {
                o = c.newInstance();
            }
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                Annotation[] annotations = field.getAnnotations();
                for (Annotation annotation : annotations) {
                    // 判斷是不是經過類型註解注入
                    if (annotation instanceof Autowired) {
                        Class classField = field.getType();
                        Object clazz = BeanFactory
                                .getBean(classField.getName());
                        field.set(o, clazz);
                        BeanFactory.addBean(classInjectPath.getName(), o);

                    }
                }
            }

        }
    }
}

PackUtils:

package com.lishun.utils;
import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
 * Description:掃描指定包工具類的註解
 * @author lishun
 * @since 2015-09-10
 */
public class PackUtils {
    public static List<Class<?>> getClassList(String packageName, boolean isRecursive) {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replaceAll("\\.", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String packagePath = url.getPath();
                        addClass(classList, packagePath, packageName, isRecursive);
                    } else if (protocol.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()) {
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            if (jarEntryName.endsWith(".class")) {
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                if (isRecursive || className.substring(0, className.lastIndexOf(".")).equals(packageName)) {
                                    classList.add(Class.forName(className));
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }

    // 獲取指定包名下的全部類(可根據註解進行過濾)
    public static List<Class<?>> getClassListByAnnotation(String packageName, Class<? extends Annotation> annotationClass) {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replaceAll("\\.", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String packagePath = url.getPath();
                        addClassByAnnotation(classList, packagePath, packageName, annotationClass);
                    } else if (protocol.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()) {
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            if (jarEntryName.endsWith(".class")) {
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                Class<?> cls = Class.forName(className);
                                if (cls.isAnnotationPresent(annotationClass)) {
                                    classList.add(cls);
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }

    private static void addClass(List<Class<?>> classList, String packagePath, String packageName, boolean isRecursive) {
        try {
            File[] files = getClassFiles(packagePath);
            if (files != null) {
                for (File file : files) {
                    String fileName = file.getName();
                    if (file.isFile()) {
                        String className = getClassName(packageName, fileName);
                        classList.add(Class.forName(className));
                    } else {
                        if (isRecursive) {
                            String subPackagePath = getSubPackagePath(packagePath, fileName);
                            String subPackageName = getSubPackageName(packageName, fileName);
                            addClass(classList, subPackagePath, subPackageName, isRecursive);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static File[] getClassFiles(String packagePath) {
        return new File(packagePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });
    }

    private static String getClassName(String packageName, String fileName) {
        String className = fileName.substring(0, fileName.lastIndexOf("."));
        if (!packageName.equals("")) {
            className = packageName + "." + className;
        }
        return className;
    }

    private static String getSubPackagePath(String packagePath, String filePath) {
        String subPackagePath = filePath;
        if (!packagePath.equals("")) {
            subPackagePath = packagePath + "/" + subPackagePath;
        }
        return subPackagePath;
    }

    private static String getSubPackageName(String packageName, String filePath) {
        String subPackageName = filePath;
        if (!packageName.equals("")) {
            subPackageName = packageName + "." + subPackageName;
        }
        return subPackageName;
    }

    private static void addClassByAnnotation(List<Class<?>> classList, String packagePath, String packageName, Class<? extends Annotation> annotationClass) {
        try {
            File[] files = getClassFiles(packagePath);
            if (files != null) {
                for (File file : files) {
                    String fileName = file.getName();
                    if (file.isFile()) {
                        String className = getClassName(packageName, fileName);
                        Class<?> cls = Class.forName(className);
                        if (cls.isAnnotationPresent(annotationClass)) {
                            classList.add(cls);
                        }
                        Field[] fields=cls.getFields();
                        for (Field field : fields) {
                            if(field.isAnnotationPresent(annotationClass)){
                                 classList.add(cls);
                            }
                        }
                    } else {
                        String subPackagePath = getSubPackagePath(packagePath, fileName);
                        String subPackageName = getSubPackageName(packageName, fileName);
                        addClassByAnnotation(classList, subPackagePath, subPackageName, annotationClass);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4 最後編寫平時使用的設計模式來測試註解(Dao,Service,Contrller)【這裏主要是爲測試註解的注入,因此沒有使用實際的使用數據庫數據,側重點不在這裏】

Dao

package com.lishun.Dao;

import com.lishun.Annotation.Service;

@Service
public class UserDao {
    public void run(){
        System.out.println("測試成功");
    }
}

Service:

package com.lishun.Service;

import com.lishun.Annotation.Autowired;
import com.lishun.Annotation.Service;
import com.lishun.Dao.UserDao;
@Service
public class UserService {
    @Autowired
    public UserDao userDao;

    public void run(){
         userDao.run();
    }

}

Controller:

package com.lishun.controller;

import com.lishun.Annotation.Autowired;
import com.lishun.Annotation.Contrller;
import com.lishun.Service.UserService;
@Contrller
public class UserContrller {
    @Autowired
    public  UserService userService;
    public void login(){
        userService.run();
    }
}

測試入口

package com.lishun.t;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import com.lishun.Annotation.Autowired;
import com.lishun.Annotation.Contrller;
import com.lishun.Annotation.Service;
import com.lishun.Dao.UserDao;
import com.lishun.Service.UserService;
import com.lishun.controller.UserContrller;
import com.lishun.factory.BeanFactory;
import com.lishun.utils.AnnotationDriven;
import com.lishun.utils.PackUtils;

public class test {

    @Test
    public void main() throws Exception {
        //啓動時根據須要掃描的包名,來注入含有註解的類的字段值
        AnnotationDriven.annotationDriven("com.lishun");
        //這裏至關於web的訪問一次controller的一次請求
        UserContrller user = (UserContrller) BeanFactory
                .getBean("com.lishun.controller.UserContrller");
        user.login();
    }

}

最後運行,

控制檯輸出:測試成功

因爲本人水平有限,若文章在表述和代碼方面若有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!但願能互相學習。須要源碼的留下郵箱

相關文章
相關標籤/搜索