我要造轮子之IoC和依赖注入
在这里造轮子的目的不是为了造出比前人更出色的轮子来,而是通过造轮子,学习轮子内部的结构及相关原理。甚至去模仿前人轮子上的优点,吸收这些优点。
这一系列文章初步估计应该包括:IoC和依赖注入、AOP、ORM、Servlet容器(tomcat)等。
类加载器
此类加载器通过getClassSet(String packageName)方法获取到packageName路径下及Jar包中的类的集合
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));
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);
Component
这是一个类注解,加上这样注解的类会在运行期交由IoC容器扫描并自动创建实例。
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 "";
Inject
这是一个属性注解,加上此注解的属性,会在运行期由IoC容器进行DI操作,为属性赋值。
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 "";
ClassFactory类查找所有basePackage包下的类,并判断这些类是否有标注@Component注解。带此注解的类是需要我们的IoC容器管理的类。
这里我们获取到的类集合是个一Map。如果用户在使用@Component注解的时候,有指定value值,我们后面在DI的时候会根据此值去查询并赋值。如果没指定value,则会使用缺省的类名。
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;
我们通过ClassFactory获取到所有托管的类后,我们可以ReflectionFactory来创建所有类的实例。
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);
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赋值)。
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);
虽然我们使用的是annotation的方式来进行管理配置信息,但像Spring一样,简单的资源文件可以使用框架更便捷与快速地工作。
我们这里使用的是Java资源文件来作为配置文件,当然如果我们有层次比较分明的配置信息时,我们也可以像Spring框架那样使用XML文件。
ResourceFactory对资源文件解析比较简单,我们现在仅仅解析scanPackage资源。这个资源告诉我们的框架需要托管的类放在哪个包路径下。当然,如果没有这个说明,我们也可以扫描整个项目下的类文件,这样效率明显比较低。
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;
ApplicationContext 通过资源管理器ResourceFactory 获取到所以需要托管类的包路径。然后交由BeanFactory创建所有的类实例,并为其属性赋值。
ApplicationContext 提供了getBean(String name),此方法用户可以通过之前定义的名称为默认的名称来获取类实例。
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);
首先我们需要一个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.");
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."); }
ApplicationContext context = ApplicationContext.getContext("context.properties"); Service service = context.getBean("Service"); service.say();
就这样,我们完成了最简单的IoC及DI框架。当然说是简单,因为它离Spring等框架的IoC功能还很远很远,包括为接口注入实现的实例、单例模式及多例模式的实现等等。
我们还要继续造轮子。
小白也看得懂的 Spring IoC 核心流程介绍 小白也看得懂的 Spring本文将用最通俗易懂的文字介绍 Spring IoC 中的核心流程,主要用于帮助初学者快速了解 IoC 的核心流程,也可以用作之前源码分析文章的总结。本着简单的初衷,本文会省略掉大量流程,只介绍最重要的步骤。 IoC 核心流程介绍
手写一个简易的IOC 这个小项目是我读过一点Spring的源码后,模仿Spring的IOC写的一个简易的IOC,当然Spring的在天上,我写的在马里亚纳海沟,哈哈
《Spring 手撸专栏》第 13 章:行云流水,把AOP动态代理,融入到Bean的生命周期 1. 工程结构 2. 定义Advice拦截器链 3. 定义 Advisor 访问者 4. 方法拦截器 5. 代理工厂 6. 融入Bean生命周期的自动代理创建者 1. 事先准备 2. 自定义拦截方法 3. spring.xml 配置 AOP 4. 单元测试
相关文章
- .net 温故知新:【7】IOC控制反转,DI依赖注入
- WEB安全基础 - - -SQL注入利用
- 实战 | SQL注入-BOOL盲注-一个小细节
- 用WriteProcessMemory做进程注入 (非DLL注入)
- SQL注入基本原理_sql到底怎么注入
- 如何用最简单的方式解释依赖注入?
- Spring依赖注入与mock
- Pikachu靶场-SQL注入-字符型注入(get)过关步骤
- JavaScript 中的依赖注入
- Spring之常用注解 注解开发 依赖注入 配置类 整合Junit......(1)
- 手工sql注入的总结
- 【Android 组件化】路由组件 ( 页面跳转参数依赖注入 )
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )
- Java 依赖注入(DI)
- Spring依赖注入
- Spring IOC/BeanFactory/ApplicationContext的工作流程/实现原理/初始化/依赖注入源码详解编程语言
- PHP防止sql语句注入终极解决方案(包含pdo各种操作使用实例)详解编程语言
- Java Spring各种依赖注入注解的区别
- Redis提供依赖注入解决方案(依赖注入redis)