該步驟實現的功能包括:java
1. 啓動程序時,將@ComponentScan加載的類,建立對象並放在容器裏面。框架
2. 經過ApplicatoinContext的getBean()方法得到容器裏面的對象。 (放在下一篇文實現)ide
1.定義一個掃描註解@ComponentScan函數
1 package ioc.core.annotation; 2 3 import java.lang.annotation.Documented; 4 import java.lang.annotation.ElementType; 5 import java.lang.annotation.Retention; 6 import java.lang.annotation.RetentionPolicy; 7 import java.lang.annotation.Target; 8 //表示用於運行時的註解 9 @Retention(RetentionPolicy.RUNTIME) 10 //表示只能在類或者接口的上面使用 11 @Target(value=ElementType.TYPE) 12 @Documented 13 public @interface ComponentScan { 14 15 /** 16 * 聲明一個註解屬性用於接收掃描的包路徑 17 * @return 18 */ 19 String[] basePackages() default {} ; 20 21 }
2.定義一個@Configuration標識配置類工具
package ioc.core.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; /** * 標識配置類註解的定義 * @author ranger * */ //表示用於運行時的註解 @Retention(RetentionPolicy.RUNTIME) //表示只能在類或者接口的上面使用 @Target(value=ElementType.TYPE) @Documented public @interface Configuration { }
2.定義容器 Context接口測試
1 package ioc.core; 2 import java.util.Map; 3 4 /** 5 * Ioc框架的容器接口 6 * @author ranger 7 * 8 */ 9 public interface Context { 10 11 /** 12 * 用於得到容器中的全部對象 13 * @return 14 */ 15 Map<String,Object> getObjects(); 16 17 /** 18 * 用於增長容器中的對象 19 * @param key 20 * @param value 21 */ 22 void addObject(String key, Object value); 23 24 }
3.定義容器操做接口ApplicationContextthis
1 package ioc.core; 2 /** 3 * Ioc框架的容器操做接口 4 * @author ranger 5 * 6 */ 7 public interface ApplicationContext { 8 9 10 /** 11 * 經過容器裏面的對象名,返回容器中的對象 12 * @param objectName 13 * @return 14 */ 15 Object getBean(String objectName); 16 }
4.實現容器url
1 package ioc.core.impl; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 import ioc.core.Context; 7 8 /** 9 * 實現框架容器,用於存儲掃描註解建立的全部對象。 10 * @author ranger 11 * 12 */ 13 public class ContextImpl implements Context { 14 15 //使用Map來存儲對象,爲何使用Map對象呢?由於預留對象名能夠設置的須要。 16 Map<String,Object> objects=new HashMap<String,Object>(); 17 18 @Override 19 public Map<String,Object> getObjects() { 20 21 return this.objects; 22 } 23 24 @Override 25 public void addObject(String key,Object value) { 26 objects.put(key, value); 27 } 28 }
5.實現掃描包。得到包以及該包子包的全部類的類全名。spa
--使用到一個工具類從包中讀取包和其子包類名--.net
1 package ioc.core.utils; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.net.JarURLConnection; 6 import java.net.URL; 7 import java.net.URLClassLoader; 8 import java.util.Enumeration; 9 import java.util.HashSet; 10 import java.util.Set; 11 import java.util.jar.JarEntry; 12 import java.util.jar.JarFile; 13 14 /** 15 * 本類用於讀取包下面的類名 16 * 來自博客 17 * http://blog.csdn.net/aust_glj/article/details/53385651 18 * 19 */ 20 public class PackageUtils { 21 public static void main(String[] args) throws Exception { 22 String packageName = "ioc.core.annotation"; 23 Set<String> classNames = getClassName(packageName, true); 24 if (classNames != null) { 25 for (String className : classNames) { 26 System.out.println(className); 27 } 28 } 29 } 30 31 /** 32 * 獲取某包下全部類 33 * @param packageName 包名 34 * @param isRecursion 是否遍歷子包 35 * @return 類的完整名稱 36 */ 37 public static Set<String> getClassName(String packageName, boolean isRecursion) { 38 Set<String> classNames = null; 39 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 40 String packagePath = packageName.replace(".", "/"); 41 42 URL url = loader.getResource(packagePath); 43 if (url != null) { 44 String protocol = url.getProtocol(); 45 if (protocol.equals("file")) { 46 classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion); 47 } else if (protocol.equals("jar")) { 48 JarFile jarFile = null; 49 try{ 50 jarFile = ((JarURLConnection) url.openConnection()).getJarFile(); 51 } catch(Exception e){ 52 e.printStackTrace(); 53 } 54 55 if(jarFile != null){ 56 getClassNameFromJar(jarFile.entries(), packageName, isRecursion); 57 } 58 } 59 } else { 60 /*從全部的jar包中查找包名*/ 61 classNames = getClassNameFromJars(((URLClassLoader)loader).getURLs(), packageName, isRecursion); 62 } 63 64 return classNames; 65 } 66 67 /** 68 * 從項目文件獲取某包下全部類 69 * @param filePath 文件路徑 70 * @param className 類名集合 71 * @param isRecursion 是否遍歷子包 72 * @return 類的完整名稱 73 */ 74 private static Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) { 75 Set<String> className = new HashSet<String>(); 76 File file = new File(filePath); 77 File[] files = file.listFiles(); 78 for (File childFile : files) { 79 if (childFile.isDirectory()) { 80 if (isRecursion) { 81 className.addAll(getClassNameFromDir(childFile.getPath(), packageName+"."+childFile.getName(), isRecursion)); 82 } 83 } else { 84 String fileName = childFile.getName(); 85 if (fileName.endsWith(".class") && !fileName.contains("$")) { 86 className.add(packageName+ "." + fileName.replace(".class", "")); 87 } 88 } 89 } 90 91 return className; 92 } 93 94 95 /** 96 * @param jarEntries 97 * @param packageName 98 * @param isRecursion 99 * @return 100 */ 101 private static Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion){ 102 Set<String> classNames = new HashSet<String>(); 103 104 while (jarEntries.hasMoreElements()) { 105 JarEntry jarEntry = jarEntries.nextElement(); 106 if(!jarEntry.isDirectory()){ 107 /* 108 * 這裏是爲了方便,先把"/" 轉成 "." 再判斷 ".class" 的作法可能會有bug 109 * (FIXME: 先把"/" 轉成 "." 再判斷 ".class" 的作法可能會有bug) 110 */ 111 String entryName = jarEntry.getName().replace("/", "."); 112 if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) { 113 entryName = entryName.replace(".class", ""); 114 if(isRecursion){ 115 classNames.add(entryName); 116 } else if(!entryName.replace(packageName+".", "").contains(".")){ 117 classNames.add(entryName); 118 } 119 } 120 } 121 } 122 123 return classNames; 124 } 125 126 /** 127 * 從全部jar中搜索該包,並獲取該包下全部類 128 * @param urls URL集合 129 * @param packageName 包路徑 130 * @param isRecursion 是否遍歷子包 131 * @return 類的完整名稱 132 */ 133 private static Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) { 134 Set<String> classNames = new HashSet<String>(); 135 136 for (int i = 0; i < urls.length; i++) { 137 String classPath = urls[i].getPath(); 138 139 //沒必要搜索classes文件夾 140 if (classPath.endsWith("classes/")) {continue;} 141 142 JarFile jarFile = null; 143 try { 144 jarFile = new JarFile(classPath.substring(classPath.indexOf("/"))); 145 } catch (IOException e) { 146 e.printStackTrace(); 147 } 148 149 if (jarFile != null) { 150 classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion)); 151 } 152 } 153 154 return classNames; 155 } 156 }
--容器操做類的公用代碼寫在AbstractApplicationContext抽象類的構造函數裏面,方便之後擴展其餘的加載狀況--
1 package ioc.core.impl; 2 3 import java.util.Iterator; 4 import java.util.Set; 5 6 import ioc.core.ApplicationContext; 7 import ioc.core.Context; 8 import ioc.core.annotation.ComponentScan; 9 import ioc.core.annotation.Configuration; 10 import ioc.core.utils.PackageUtils; 11 12 public abstract class AbstractApplicationContext implements ApplicationContext{ 13 //聲明一個線程變量,存儲容器對象,表示同一條線程,一個ApplicationContext只操做一個容器對象。 14 private ThreadLocal<Context> contexts=new ThreadLocal<Context>(); 15 16 17 protected String[] basePackage=null; 18 /** 19 * 將容器操做加載建立對象的代碼寫抽象類裏面,這樣能夠方便之後擴展多種實現。 20 * @param classType 21 */ 22 public AbstractApplicationContext(Class<?> classType) { 23 //判斷配置類是否有Configuration註解 24 Configuration annotation = classType.getAnnotation(Configuration.class); 25 if(annotation!=null){ 26 //得到組件掃描註解 27 ComponentScan componentScan = classType.getDeclaredAnnotation(ComponentScan.class); 28 //得到包名 29 this.basePackage = componentScan.basePackages(); 30 //根據包名得到類全限制名 31 Set<String> classNames = PackageUtils.getClassName(this.basePackage[0], true); 32 //經過類名建立對象 33 Iterator<String> iteratorClassName = classNames.iterator(); 34 while(iteratorClassName.hasNext()){ 35 String className = iteratorClassName.next(); 36 try { 37 //經過類全名建立對象 38 Object instance = Class.forName(className).newInstance(); 39 //將對象加到容器中,對象名就類全名 40 this.getContext().addObject(instance.getClass().getSimpleName(),instance); 41 } catch (InstantiationException e) { 42 e.printStackTrace(); 43 } catch (IllegalAccessException e) { 44 e.printStackTrace(); 45 } catch (ClassNotFoundException e) { 46 e.printStackTrace(); 47 } 48 } 49 } 50 } 51 52 public String[] getBasePackage() { 53 return basePackage; 54 } 55 56 57 public Context getContext(){ 58 if(contexts.get()==null){ 59 //調用容器 60 Context context=new ContextImpl(); 61 contexts.set(context); 62 } 63 return contexts.get(); 64 } 65 66 67 68 }
----實現AnnotationApplicationContext註解操做類,繼承bstractApplicationContext---
注意:這裏尚未實現getBean方法。先實現啓動程序時,包下面的全部類有沒有加入到容器裏了--
1 package ioc.core.impl; 2 3 public class AnntationApplicationContext extends AbstractApplicationContext { 4 5 public AnntationApplicationContext(Class<?> classType) { 6 super(classType); 7 } 8 9 @Override 10 public Object getBean(String objectName) { 11 12 return null; 13 } 14 }
測試是否能夠得到指定掃描包下的類的對象
1.建立一個測試源碼包存放測試代碼
2. Config類,是一個配置類。裏面定義掃描包的路徑
1 package ioc.core.test.config; 2 3 import ioc.core.annotation.ComponentScan; 4 import ioc.core.annotation.Configuration; 5 6 //使用定義@Configuration定義該類是一個配置類 7 @Configuration 8 //使用ComponentScan設置掃描包的路徑 9 @ComponentScan(basePackages="ioc.core.test") 10 public class Config { 11 12 }
3. 編寫一個普通的UserService類測試
package ioc.core.test.service; /** * 一個普通的類,用於測試是否能夠建立對象 * @author ranger * */ public class UserService { public void login(){ System.out.println("-登陸-"); } }
4. 建立一個AnntationApplicationContextTest測試類
package ioc.core.test; import org.junit.Test; import ioc.core.impl.AnntationApplicationContext; import ioc.core.test.config.Config; public class AnntationApplicationContextTest { @Test public void constructor(){ try { AnntationApplicationContext context=new AnntationApplicationContext(Config.class); //若是能夠打印出容器裏面的對象,說明成功 System.out.println(context.getContext().getObjects()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
5.測試結果,得到UserService的對象。成功!