最近在看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