最近在看spring的源码,想写一系列的文章,来模拟spring的一些最基本功能的实现。
部分源码是从spring中抽取出来的,然后进行了无限的精简,目的是让入门的弟兄们也可以看得很清楚。
首先这第一篇,就是说的spring的IOC功能中,是如何实现通过注解来加载Bean文件的。
文章原创,转载请注明出处www.neohope.com
经过无限精简之后,整体流程为:
1、初始化bean工厂
bean工厂根据配置,加载带有指定annotation的类,并放到了map中
2、从bean工厂获取一个bean
通过bean的id,实例化一个类,并返回
需要的前置知识为:
1、spring的基本知识
2、反射
然后是源码:
1、TestAnnotation.java
这是个注解类,声明了一个新的注解类型,用于表示bean及bean的名字
package com.neohope.annotation.test; import java.io.File; import java.io.IOException; import java.lang.annotation.*; import java.net.JarURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Created by Hansen on 2016/3/10. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface TestAnnotation { String ntype(); String nname(); String nauthor(); String nversion(); String nmsg(); }
2、TestBean.java
这是个Bean,使用了注解作为标识,是用于具体加载的类
package com.neohope.annotation.test; /** * Created by Hansen on 2016/3/10. */ @TestAnnotation(nauthor = "neohope",nversion="1.0",ntype = "BEAN",nname = "testbean", nmsg = "this is just a message") public class TestBean { public String name; public int age; public String sex; }
3、ClassPathScanner.java
从spring中抽取的,类扫描工具类
其作用为,通过指定包名,扫描包名下所有的类
可用于jar文件中类的扫描
package com.neohope.annotation.test; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Created by Hansen on 2016/3/10. */ public class ClassPathScanner { /** * Get all class name in given packageName * @param packageName the package name * @return the result as String array * @throws IOException in case of I/O errors * @see #getAllClassFileInPackage */ public static String[] getAllClassNameInPackage(String packageName) throws IOException, URISyntaxException { String[] classFiles = getAllClassFileInPackage(packageName); Set<String> result = new LinkedHashSet<String>(classFiles.length); for(String clazz : classFiles) { result.add(clazz.replace(".class", "").replace("/", ".").replace("\\", ".")); } return result.toArray(new String[result.size()]); } /** * Get all class files in given packageName * @param packageName the package name * @return the result as String array * @throws IOException in case of I/O errors * @see #findAllClassPathByPackageName * @see #loadJarClasses * @see #loadFileClasses */ protected static String[] getAllClassFileInPackage(String packageName) throws IOException, URISyntaxException { if (packageName.startsWith("/")) { packageName = packageName.substring(1); } if (packageName.endsWith("/")) { packageName = packageName.substring(0,packageName.length()-1); } URL[] rootDirResources = findAllClassPathByPackageName(packageName); Set<String> result = new LinkedHashSet<String>(16); for (URL rootDirResource : rootDirResources) { if (rootDirResource.toString().toUpperCase().startsWith("JAR")) { result.addAll(loadJarClasses(rootDirResource)); } else { String rootEntryPath = rootDirResource.getPath().replace(packageName,"").replace("/",File.separator).substring(1); result.addAll(loadFileClasses(rootDirResource,rootEntryPath)); } } return result.toArray(new String[result.size()]); } /** * Find all class path with the given packageName via the ClassLoader. * @param packageName the absolute path within the classpath * @return the result as URL array * @throws IOException in case of I/O errors * @see java.lang.ClassLoader#getResources */ protected static URL[] findAllClassPathByPackageName(String packageName) throws IOException { ClassLoader cl = ClassPathScanner.class.getClassLoader(); Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(packageName) : ClassLoader.getSystemResources(packageName)); Set<URL> result = new LinkedHashSet<URL>(); while (resourceUrls.hasMoreElements()) { URL url = resourceUrls.nextElement(); result.add(url); } return result.toArray(new URL[result.size()]); } /** * Get all the class file in given class dir * @param rootClassDir the root directory * @return all the class in rootClassDir * @throws IOException if directory contents could not be retrieved */ protected static Set<String> loadFileClasses(URL rootClassDir, String rootEntryPath) throws IOException, URISyntaxException { File rootDir; rootDir = new File(rootClassDir.toURI()); if(!rootDir.exists() || !rootDir.isDirectory() || !rootDir.canRead()) { return Collections.emptySet(); } Set<String> result = new LinkedHashSet<String>(8); EnumClassFiles(rootDir, rootEntryPath, result); return result; } /** * Recursively get all class files in the given dir * @param dir the given directory * @param result the Set of class names to add to * @throws IOException if directory contents could not be retrieved */ protected static void EnumClassFiles(File dir, String rootEntryPath, Set<String> result) throws IOException { File[] dirContents = dir.listFiles(); if (dirContents == null) { return; } for (File content : dirContents) { if (content.isDirectory()) { if (!content.canRead()) { } else { EnumClassFiles(content, rootEntryPath, result); } } else { if(content.toString().endsWith(".class")) { result.add(content.toString().replace(rootEntryPath, "")); } } } } /** * Get all the class file in given jar path * @param jarPath the given jar path * @return the Set of matching Resource instances * @throws IOException in case of I/O errors */ protected static Set<String> loadJarClasses(URL jarPath) throws IOException { JarFile jarFile; String jarFileUrl; String rootEntryPath = ""; Set<String> result = new LinkedHashSet<String>(8); URLConnection connection = jarPath.openConnection(); if (connection instanceof JarURLConnection) { JarURLConnection jarCon = (JarURLConnection) connection; jarFile = jarCon.getJarFile(); jarFileUrl = jarCon.getJarFileURL().toExternalForm(); JarEntry jarEntry = jarCon.getJarEntry(); rootEntryPath = (jarEntry != null ? jarEntry.getName() : ""); } else { return Collections.emptySet(); } try { for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) { JarEntry entry = entries.nextElement(); String entryPath = entry.getName(); if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) { result.add(entryPath); } } } catch (Exception ex) { } return result; } public static void main(String[] args) throws IOException, URISyntaxException { //String[] classes = getAllClassNameInPackage("com/neohope/annotation"); //String[] classes = getAllClassNameInPackage("org/apache/log4j/config"); String[] classes = getAllClassNameInPackage("/com/neohope/annotation/"); //String[] classes = getAllClassNameInPackage("org/apache/log4j/config"); for(String clazz : classes) { System.out.println(clazz); } } }
4、ClassAnnotationScanner.java
工厂类,初始化时,通过annotation过滤bean,并将bean的名称及class放到map中
获取实例时,通过bean名称,获取class,并实例化,返回
package com.neohope.annotation.test; import java.io.IOException; import java.net.URISyntaxException; import java.util.HashMap; /** * Created by Hansen on 2016/3/10. */ public class ClassAnnotationScanner { /** * HashMap of bean name and bean class */ static HashMap<String, Class> beanMap = new HashMap<String, Class>(); /** * Load all bean class with annotation from package * @param packageName the package name * @return void * @throws IOException in case of I/O errors * @throws URISyntaxException in case of URI syntax error * @throws ClassNotFoundException in case of class not found * @see ClassPathScanner#getAllClassNameInPackage */ protected static void loadBeanClasses(String packageName) throws IOException, URISyntaxException, ClassNotFoundException { String[] classes = ClassPathScanner.getAllClassNameInPackage(packageName); for(String clazz : classes) { Class loadedClazz = Class.forName(clazz); Object obj = loadedClazz.getAnnotation(TestAnnotation.class); if(obj==null)continue; TestAnnotation ta = (TestAnnotation)obj; String beanName = ta.nname(); beanMap.put(beanName,loadedClazz); } } /** * init the bean factory * @param packageName the package name * @return void * @throws IOException in case of I/O errors * @throws URISyntaxException in case of URI syntax error * @throws ClassNotFoundException in case of class not found * @see #loadBeanClasses */ public static void initBeanFactory(String packageName) throws IOException, URISyntaxException, ClassNotFoundException { loadBeanClasses(packageName); } /** * get bean by bean name * @param beanName the bean name * @return new object of the bean class * @throws IllegalAccessException in case of illegal access * @throws InstantiationException in case of instantiation error */ protected static Object getBean(String beanName) throws IllegalAccessException, InstantiationException { Class loadedClazz = beanMap.get(beanName); if(loadedClazz!=null) { return loadedClazz.newInstance(); } else { return null; } } public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InstantiationException, IllegalAccessException { initBeanFactory("com/neohope/annotation/"); TestBean bean = (TestBean)getBean("testbean"); bean.name = "neohope"; } }
文章原创,转载请注明出处www.neohope.com