1、前言
因为这是我设想要写的一系列文章的第一篇。所以我先说明一下我为什么要重复造轮子。
在这里造轮子的目的不是为了造出比前人更出色的轮子来,而是通过造轮子,学习轮子内部的结构及相关原理。甚至去模仿前人轮子上的优点,吸收这些优点。
这一系列文章初步估计应该包括:IoC和依赖注入、AOP、ORM、Servlet容器(tomcat)等。
2、IoC和依赖注入的概念
Inverse of Control,控制反转。
IoC主要功能是依赖关系的转移。应用的本身不负责依赖对象的创建和维护,而是由第三方容器负责。控制权就由应用转移到了外部容器。
IoC的主要功能由控制反转来解释并不是很好理解。所以提出了新的概念Dependency Injection.
DI依赖注入,调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以转移对某一接口实现类的依赖。也就是在运行期,由外部容器动态地将所依赖的对象注入到组件中去。
最常用的IoC和DI框架没有之一:Spring。
Spring IoC的介绍和使用: http://www.jb51.cc/article/p-hdyilwng-ny.html
3、源码结构
本文中使用的是通过注解(annotation)的方式来对需要进行IoC和DI的类进行管理。
我要看大图:http://img.blog.csdn.net/20160218111901359
4、实现过程
4.1 工具类
首先我们需要两个工具类:类加载器ClassLoader 和反射工厂类:
package xyz.letus.framework.ioc;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** * 类加载器 * @ClassName: ClassLoader * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月14日 * */
public class ClassLoader {
public static final Logger LOGGER = LoggerFactory.getLogger(ClassLoader.class);
/** * 获取线程上的类加载器 * @Title: getClassLoader * @Description: TODO * @param @return * @return java.lang.ClassLoader * @throws */
public static java.lang.ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
/** * 加载类 * @Title: loadClass * @Description: TODO * @param @param className * @param @param isInitialized * @param @return * @return Class<?> * @throws */
public static Class<?> loadClass(String className,boolean isInitialized){
Class<?> clazz = null;
try {
clazz = Class.forName(className,isInitialized,getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error("load class failure",e);
throw new RuntimeException(e);
}
return clazz;
}
/** * 获取包下所有的类 * @Title: getClassSet * @Description: TODO * @param @param packageName * @param @return * @return Set<Class<?>> * @throws */
public static Set<Class<?>> getClassSet(String packageName){
Set<Class<?>> classes = new HashSet<Class<?>>();
try {
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
while(urls.hasMoreElements()){
URL url = urls.nextElement();
if(url != null){
String protocol = url.getProtocol();
if(protocol.equals("file")){
String packagePath = url.getPath().replace("%20"," ");
addCommonClass(classes,packagePath,packageName);
}else if(protocol.equals("jar")){
addJarClasses(classes,url);
}
}
}
} catch (IOException e) {
LOGGER.error("get class set failure",e);
e.printStackTrace();
throw new RuntimeException(e);
}
return classes;
}
/** * 把jar包中所有的类加入集合 * @Title: addJarClasses * @Description: TODO * @param @param classes * @param @param url * @param @throws IOException * @return void * @throws */
private static void addJarClasses(Set<Class<?>> classes,URL url)
throws IOException {
JarURLConnection connetion = (JarURLConnection) url.openConnection();
if(connetion != null){
JarFile jar = connetion.getJarFile();
if(jar != null){
Enumeration<JarEntry> enties = jar.entries();
while(enties.hasMoreElements()){
JarEntry entry = enties.nextElement();
String entryName = entry.getName();
if(entryName.endsWith(".class")){
String className = entryName.substring(0,entryName.lastIndexOf('.')).replaceAll("/",".");
doAddClass(classes,className);
}
}
}
}
}
/** * 把文件夹中的所有类加入集合 * @Title: addCommonClass * @Description: TODO * @param @param classes * @param @param packagePath * @param @param packageName * @return void * @throws */
private static void addCommonClass(Set<Class<?>> classes,String packagePath,String packageName){
File[] files = new File(packagePath).listFiles(new FileFilter() {
public boolean accept(File file) {
// TODO Auto-generated method stub
return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
}
});
for(File file : files){
String fileName = file.getName();
if(file.isFile()){
String className = packageName + '.' + fileName.substring(0,fileName.lastIndexOf('.'));
doAddClass(classes,className);
}else{
String subPackagetPath = packagePath + "/" + fileName;
String subPackagetName = packageName + "/" + fileName;
addCommonClass(classes,subPackagetPath,subPackagetName);
}
}
}
/** * 加入类到集合 * @Title: doAddClass * @Description: TODO * @param @param classes * @param @param className * @return void * @throws */
private static void doAddClass(Set<Class<?>> classes,String className){
classes.add(loadClass(className,false));
}
}
package xyz.letus.framework.ioc;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** * 反射工厂 * @ClassName: ReflectionFactory * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月19日 * */
public class ReflectionFactory {
public static final Logger LOGGER = LoggerFactory.getLogger(ReflectionFactory.class);
/** * 创建实例 * @Title: newInstance * @Description: TODO * @param @param clazz * @param @return * @return Object * @throws */
public static Object newInstance(Class<?> clazz){
Object instance = null;
try {
instance = clazz.newInstance();
} catch (Exception e) {
LOGGER.error("new instatnce failure",e);
throw new RuntimeException(e);
}
return instance;
}
/** * 调用方法 * @Title: invokeMethod * @Description: TODO * @param @param obj * @param @param method * @param @param args * @param @return * @return Object * @throws */
public static Object invokeMethod(Object obj,Method method,Object...args){
Object result = null;
try {
method.setAccessible(true);
result = method.invoke(obj,args);
} catch (Exception e) {
LOGGER.error("invoke method failure",e);
throw new RuntimeException(e);
}
return result;
}
/** * 设置成员变量值 * @Title: setField * @Description: TODO * @param @param obj * @param @param field * @param @param value * @return void * @throws */
public static void setField(Object obj,Field field,Object value){
try {
field.setAccessible(true);
field.set(obj,value);
} catch (Exception e) {
LOGGER.error("set field failure",e);
throw new RuntimeException(e);
}
}
}
4.2 注解类
声明两个注解类:Component和Inject
package xyz.letus.framework.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 组件描述注解 * @ClassName: Component * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月19日 * */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
package xyz.letus.framework.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 依赖注入注解 * @ClassName: Controller * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月19日 * */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
String value() default "";
}
4.3 扫描托管的类
ClassFactory类查找所有basePackage包下的类,并判断这些类是否有标注@Component注解。带此注解的类是需要我们的IoC容器管理的类。
这里我们获取到的类集合是个一Map。如果用户在使用@Component注解的时候,有指定value值,我们后面在DI的时候会根据此值去查询并赋值。如果没指定value,则会使用缺省的类名。
package xyz.letus.framework.ioc;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import xyz.letus.framework.ioc.annotation.Component;
/** * 类操作助手 * @ClassName: ClassHelper * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月19日 * */
public class ClassFactory {
/** * 获取交由容器管理的类 * @Title: getBeanClasses * @Description: TODO * @param @return * @return Map<String,Class<?>> * @throws */
public static Map<String,Class<?>> getBeanClasses(String basePackage){
Map<String,Class<?>> annotationClasses = new HashMap<String,Class<?>>();
Set<Class<?>> classes = ClassLoader.getClassSet(basePackage);
for(Class<?> clazz : classes){
if(clazz.isAnnotationPresent(Component.class)){
Component component = clazz.getAnnotation(Component.class);
String name = clazz.getSimpleName();
String value = component.value();
if(value.length() > 0){
name = value;
}
classes.add(clazz);
annotationClasses.put(name,clazz);
}
}
return annotationClasses;
}
}
4.4 创建实例及属性赋值
我们通过ClassFactory获取到所有托管的类后,我们可以ReflectionFactory来创建所有类的实例。
Object obj = ReflectionFactory.newInstance(entry.getValue());
package xyz.letus.framework.ioc;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/** * Bean助手类 * @ClassName: BeanHelper * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月19日 * */
public class beanfactory {
private static final Map<String,Object> BEAN_MAP = new HashMap<String,Object>();
/** * 创建所有托管的实例 * @Title: createInstance * @Description: TODO * @param @param packages * @return void * @throws */
public static void createInstance(List<String> packages){
for (String packagePath : packages) {
Map<String,Class<?>> beanClasses = ClassFactory
.getBeanClasses(packagePath);
for (Entry<String,Class<?>> entry : beanClasses.entrySet()) {
Object obj = ReflectionFactory.newInstance(entry.getValue());
BEAN_MAP.put(entry.getKey(),obj);
}
}
IocHelper.inject(BEAN_MAP);
}
/** * 获取Bean实例 * @Title: getBean * @Description: TODO * @param @param name * @param @return * @return T * @throws */
@SuppressWarnings("unchecked")
public static <T> T getBean(String name){
if(!BEAN_MAP.containsKey(name)){
throw new RuntimeException("can not get bean by className:"+name);
}
return (T) BEAN_MAP.get(name);
}
}
实例化所有类后,由IocHelper类来判断这些对象中是否有带@Inject注解的属性,然后同样通过ReflectionFactory来为这些属性进行依赖注入(DI赋值)。
package xyz.letus.framework.ioc;
import java.lang.reflect.Field;
import java.util.Map;
import xyz.letus.framework.ioc.annotation.Inject;
/** * 依赖注入助手类 * @ClassName: IocHelper * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月19日 * */
public class IocHelper {
/** * 注入属性 * @Title: inject * @Description: TODO * @param * @return void * @throws */
public static void inject(Map<String,Object> beanMap){
/** * 为类中的属性注入值 */
for(Map.Entry<String,Object> entry : beanMap.entrySet()){
String name = entry.getKey();
Object beanInstance = entry.getValue();
Field[] beanFields = beanInstance.getClass().getDeclaredFields();
for(Field field : beanFields){
if(field.isAnnotationPresent(Inject.class)){
Inject inject = field.getAnnotation(Inject.class);
if(inject.value().length() > 0){
name = inject.value();
}
Class<?> fieldClazz = field.getType();
Object fieldInstance = beanMap.get(name);
if(fieldInstance != null){
ReflectionFactory.setField(beanInstance,field,fieldInstance);
}
}
}
}
}
}
4.4 资源管理器
虽然我们使用的是annotation的方式来进行管理配置信息,但像Spring一样,简单的资源文件可以使用框架更便捷与快速地工作。
我们这里使用的是Java资源文件来作为配置文件,当然如果我们有层次比较分明的配置信息时,我们也可以像Spring框架那样使用XML文件。
ResourceFactory对资源文件解析比较简单,我们现在仅仅解析scanPackage资源。这个资源告诉我们的框架需要托管的类放在哪个包路径下。当然,如果没有这个说明,我们也可以扫描整个项目下的类文件,这样效率明显比较低。
package xyz.letus.framework.context;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
/** * 资源管理器 * @ClassName: ResourceFactory * @Description: TODO * @author 潘广伟(笨笨) * @date 2016年2月17日 * */
public class ResourceFactory {
private static final List<String> SCAN_PACKAGES = new ArrayList<String>();
/** * 解析资源文件(配置文件) * @Title: parse * @Description: TODO * @param @param fileName * @param @throws FileNotFoundException * @param @throws IOException * @return void * @throws */
public static void parse(String fileName) throws FileNotFoundException,IOException {
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
Properties properties = new Properties();
properties.load(stream);
Enumeration<?> e = properties.propertyNames();// 得到配置文件的名字
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
String value = properties.getProperty(key);
if("scanPackage".equals(key)){
SCAN_PACKAGES.add(value);
}
}
}
/** * 获取自动描述的包路径 * @Title: getPackages * @Description: TODO * @param @return * @return List<String> * @throws */
public static List<String> getPackages(){
return SCAN_PACKAGES;
}
}
4.5 应用容器
ApplicationContext 通过资源管理器ResourceFactory 获取到所以需要托管类的包路径。然后交由beanfactory创建所有的类实例,并为其属性赋值。
ApplicationContext 提供了getBean(String name),此方法用户可以通过之前定义的名称为默认的名称来获取类实例。
package xyz.letus.framework.context;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import xyz.letus.framework.ioc.beanfactory;
/** * 应用容器 * @ClassName: IocContext * @Description: TODO * @author 潘广伟(笨笨) * @date 2016年2月17日 * */
public class ApplicationContext {
private String path;
private ApplicationContext(String path){
this.path = path;
init();
}
public static ApplicationContext getContext(String path){
return new ApplicationContext(path);
}
/** * 初始化 * @Title: init * @Description: TODO * @param * @return void * @throws */
public void init(){
try {
ResourceFactory.parse(path);
List<String> packages = ResourceFactory.getPackages();
beanfactory.createInstance(packages);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/** * 根据别名获取一个对象 * @Title: getBean * @Description: TODO * @param @param name * @param @return * @return T * @throws */
public <T> T getBean(String name) {
return beanfactory.getBean(name);
}
}
5、框架使用
现在我们使用本文编写的IoC框架进行编程。
- 首先我们需要一个Dao类及一个Service类。
Dao类比较简单,一个say()方法,并加上@Component将由IoC容器管理。
Service和Dao不同的是,它有一个未初始化的dao属性,并由@Inject告诉IoC容器,需要在运行时,为此属性初始化。
package xyz.letus.demo;
import xyz.letus.framework.ioc.annotation.Component;
@Component
public class Dao {
public void say(){
System.out.println("Dao say something.");
}
}
package xyz.letus.demo;
import xyz.letus.framework.ioc.annotation.Component;
import xyz.letus.framework.ioc.annotation.Inject;
@Component
public class Service {
@Inject
private Dao dao;
public void say(){
dao.say();
System.out.println("Service say something.");
}
}
- 资源文件context.properties
scanPackage=xyz.letus.demo
- 启动IoC容器,并获取service实例
ApplicationContext context = ApplicationContext.getContext("context.properties");
Service service = context.getBean("Service");
service.say();
- 输出结果
Dao say something.
Service say something.
当然我们上面使用的注解都是缺省模式的。我们也可以为托管的对象加上别名:
@Component("a")
public class Dao {
@Inject("a")
private Dao dao;
@Component("b")
public class Service {
Service service = context.getBean("b");
得到的结果是一样的。
6、我们还要继续
就这样,我们完成了最简单的IoC及DI框架。当然说是简单,因为它离Spring等框架的IoC功能还很远很远,包括为接口注入实现的实例、单例模式及多例模式的实现等等。
我们还要继续造轮子。
7、源码下载
https://github.com/benben-ren/wheel/tree/d389e62b1f1380b45bb38b098c5ed0ce8123c9dd
注:源码中只有ioc构架源码,不包含使用源码。