zl程序教程

您现在的位置是:首页 >  后端

当前栏目

我要造轮子之IoC和依赖注入

注入依赖 IOC 轮子
2023-09-14 08:57:15 时间
div >因为这是我设想要写的一系列文章的第一篇。所以我先说明一下我为什么要重复造轮子。

在这里造轮子的目的不是为了造出比前人更出色的轮子来,而是通过造轮子,学习轮子内部的结构及相关原理。甚至去模仿前人轮子上的优点,吸收这些优点。

这一系列文章初步估计应该包括: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. 单元测试