手寫@Component和@Resource實現IoC和DI

我的博客:zhenganwen.topphp

IoC & DI

IoCInversion of Control,控制反轉,核心思想是面向接口編程,讓上下游組件面向接口編程從而實現業務的靈活切換(上下游組件解耦)。java

DIDependency Injection,依賴注入,全部的基礎組件都面向接口編程,由Spring幫咱們注入接口實現類對象。spring

Controller層和Service層爲例,Controller爲調用方,是爲上游。若是按照傳統方式,在Controller中顯式地經過new來引入某Service進行編程,那麼這二者是緊耦合的:編程

public class UserController{
    
    UserService userService = new UserService();
    
    public void add(User user){
        return userService.add(User user);
    }
    
    public void query(){
        return userService.query();
    }
}

public class UserService{
    
    public void add(User user){
        // insert user
    }
    
    public void query(){
        // select user
    }
}
複製代碼

如此,一旦業務變動就須要更改UserService的代碼。如如今系統要作讀寫分離,那麼就須要將UserService修改以下:緩存

public class UserService{
    
    public void add(User user){
        // insert user in db_1
    }
    
    public void query(){
        // select user in db_2
    }
}
複製代碼

這違背了「開閉原則」(對擴展開發對修改關閉)。服務器

因而在Spring中,全部的基礎組件都面向接口編程,將基礎組件的實例交給Spring建立和管理。經過DI(依賴注入)來靈活地控制組件之間的依賴關係。app

首先針對特定的業務域定義統一的接口:dom

public interface IUserService{
    void add();
    
    void query();
}
複製代碼

根據接口能夠有不一樣的實現類,如讀寫同庫、讀寫分離:工具

public class UserServiceImpl implements IUserService{
    public void add(User user){
        // insert user in db_1
    }
    
    public void query(){
        // select user in db_1
    }
}

public class UserReadWriteServiceImpl implements IUserService{
     public void add(User user){
        // insert user in db_1
    }
    
    public void query(){
        // select user in db_2
    }
}
複製代碼

將上述基礎業務組件歸入SpringIoC容器管理測試

<bean id="userServiceImpl" class="xx.xx.xx.UserServiceImpl"></bean>
<bean id="userServiceReadWriteImpl" class="xx.xx.xx.UserServiceReadWriteImpl"></bean>
複製代碼

如此在上游組件中能夠作到靈活切換:

@Controller
public class UserController{
    
    @Resource(name = "userServiceImpl")
    //@Resource(name = "userServiceReadWriteImpl")
    UserService userService;
    
     public void add(User user){
        return userService.add(User user);
    }
    
    public void query(){
        return userService.query();
    }
}
複製代碼

這種編程模式在業務變動時,只需擴展一個UserServiceReadWriteImpl,並經過@Resource更改注入源就可實現業務的切換,而無需更改原有的UserServiceImpl的代碼。

手寫xml版本IoC容器

IoC容器:

package cn.tuhu.springioc.ioc;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class CustomClassPathXmlApplicationContext {

    private String xmlPath;

    private static ConcurrentHashMap<String, Object> beanFactory;

    /** * 實例化bean * 提取xml中的<bean></bean>節點,根據其中的class屬性實例化bean,以beanId爲緩存到beanFactory * 注入依賴 * 遍歷bean的屬性,將標註有依賴注入的屬性到beanFactory中找相關依賴賦值 * @param xmlPath */
    public CustomClassPathXmlApplicationContext(String xmlPath) {
        this.xmlPath = xmlPath;
        beanFactory = new ConcurrentHashMap<String, Object>();
        try {
            initBeanFactory();
        } catch (DocumentException e) {
            System.out.println("xml解析失敗,請檢查編寫是否正確");
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            System.out.println("bean配置的class不存在");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.out.println("bean初始化失敗,請確保有無參構造方法");
            e.printStackTrace();
        }
    }

    public Object getBean(String beanId) {
        return beanFactory.get(beanId);
    }

    private List<Element> parseXml() throws DocumentException {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(xmlPath);
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element rootElement = document.getRootElement();
        List<Element> beanElements = rootElement.elements();
        return beanElements;
    }

    private void initBeanFactory() throws DocumentException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        List<Element> beanElements = parseXml();
        for (Element beanElement : beanElements) {
            String beanId = beanElement.attributeValue("id");
            String className = beanElement.attributeValue("class");
            Class<?> clz = Class.forName(className);
            beanFactory.put(beanId, clz.newInstance());
        }
    }

}
複製代碼

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="cn.tuhu.springioc.service.impl.UserServiceImpl"/>
</beans>
複製代碼

組件:

package cn.tuhu.springioc.service;

public interface UserService {

    void add();
}


package cn.tuhu.springioc.service.impl;

import cn.tuhu.springioc.service.UserService;

public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("insert user");
    }
}
複製代碼

測試:

package cn.tuhu.springioc.service.ioc;

import cn.tuhu.springioc.ioc.CustomClassPathXmlApplicationContext;
import cn.tuhu.springioc.service.UserService;
import org.junit.Test;

public class CustomClassPathXmlApplicationContextTest {

    @Test
    public void getBean() {
        CustomClassPathXmlApplicationContext context = new CustomClassPathXmlApplicationContext("spring.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}


insert user
複製代碼

手寫註解版IoC容器和注入註解

自定義註解

組件註解

package cn.tuhu.springioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//能夠在類上標註
@Target(ElementType.TYPE)
//運行時保留此註解信息
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomComponent {
}
複製代碼

依賴注入

package cn.tuhu.springioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//能夠在成員變量上標註
@Target(ElementType.FIELD)
//運行時保留此註解信息
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomResource {
}
複製代碼

IoC容器

package cn.tuhu.springioc.ioc;

import cn.tuhu.springioc.annotation.CustomComponent;
import cn.tuhu.springioc.annotation.CustomResource;
import cn.tuhu.springioc.util.ClassUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class CustomAnnotationApplicationContext {

    private String basePackage;

    private static ConcurrentHashMap<String, Object> beanFactory = new ConcurrentHashMap<String, Object>();

    public CustomAnnotationApplicationContext(String basePackage) {
        this.basePackage = basePackage;
        this.initBeans();
        Collection<Object> beans = beanFactory.values();
        for (Object bean : beans) {
            this.injectDependencies(bean);
        }
    }

    private void injectDependencies(Object bean) {
        Class<?> clz = bean.getClass();
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            CustomResource annotation = field.getAnnotation(CustomResource.class);
            if (annotation != null) {
                //若是標註了@CustomResource註解,則須要依賴注入
                Object obj = beanFactory.get(field.getName());
                field.setAccessible(true);//若是訪問權限不夠,須要設置此項
                try {
                    field.set(bean, obj);   //依賴注入
                } catch (IllegalAccessException e) {   //設置了Accessible則不會拋此異常
                    e.printStackTrace();
                }
            }
        }
    }

    private void initBeans() {
        List<Class<?>> classes = ClassUtils.getClasses(basePackage);	//ClassUtils工具類見下
        for (Class<?> clz : classes) {
            CustomComponent annotation = clz.getAnnotation(CustomComponent.class);
            if (annotation != null) {
                // 標註了@CustomComponent註解,須要歸入IoC容器管理
                Object bean = null;
                try {
                    bean = clz.newInstance();
                } catch (InstantiationException e) {
                    System.out.printf("實例化bean:%s 失敗", clz.toString());
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    System.out.printf("%s訪問權限不夠", clz.toString());
                    e.printStackTrace();
                }
                String beanId = this.toLowerCaseFirstChar(clz.getSimpleName());
                beanFactory.put(beanId, bean);
            }
        }
    }

    public Object getBean(String beanId) {
        return beanFactory.get(beanId);
    }

    private String toLowerCaseFirstChar(String className) {
        StringBuilder stringBuilder = new StringBuilder(className.substring(0,1).toLowerCase());
        stringBuilder.append(className.substring(1));
        return stringBuilder.toString();
    }
}
複製代碼

經過反射掃描某package下全部類的工具類

package cn.tuhu.springioc.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtils {

	/** * 取得某個接口下全部實現這個接口的類 */
	public static List<Class> getAllClassByInterface(Class c) {
		List<Class> returnClassList = null;

		if (c.isInterface()) {
			// 獲取當前的包名
			String packageName = c.getPackage().getName();
			// 獲取當前包下以及子包下因此的類
			List<Class<?>> allClass = getClasses(packageName);
			if (allClass != null) {
				returnClassList = new ArrayList<Class>();
				for (Class classes : allClass) {
					// 判斷是不是同一個接口
					if (c.isAssignableFrom(classes)) {
						// 自己不加入進去
						if (!c.equals(classes)) {
							returnClassList.add(classes);
						}
					}
				}
			}
		}

		return returnClassList;
	}

	/* * 取得某一類所在包的全部類名 不含迭代 */
	public static String[] getPackageAllClassName(String classLocation, String packageName) {
		// 將packageName分解
		String[] packagePathSplit = packageName.split("[.]");
		String realClassLocation = classLocation;
		int packageLength = packagePathSplit.length;
		for (int i = 0; i < packageLength; i++) {
			realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
		}
		File packeageDir = new File(realClassLocation);
		if (packeageDir.isDirectory()) {
			String[] allClassName = packeageDir.list();
			return allClassName;
		}
		return null;
	}

	/** * 從包package中獲取全部的Class * * @param pack * @return */
	public static List<Class<?>> getClasses(String packageName) {

		// 第一個class類的集合
		List<Class<?>> classes = new ArrayList<Class<?>>();
		// 是否循環迭代
		boolean recursive = true;
		// 獲取包的名字 並進行替換
		String packageDirName = packageName.replace('.', '/');
		// 定義一個枚舉的集合 並進行循環來處理這個目錄下的things
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
			// 循環迭代下去
			while (dirs.hasMoreElements()) {
				// 獲取下一個元素
				URL url = dirs.nextElement();
				// 獲得協議的名稱
				String protocol = url.getProtocol();
				// 若是是以文件的形式保存在服務器上
				if ("file".equals(protocol)) {
					// 獲取包的物理路徑
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					// 以文件的方式掃描整個包下的文件 並添加到集合中
					findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
				} else if ("jar".equals(protocol)) {
					// 若是是jar包文件
					// 定義一個JarFile
					JarFile jar;
					try {
						// 獲取jar
						jar = ((JarURLConnection) url.openConnection()).getJarFile();
						// 今後jar包 獲得一個枚舉類
						Enumeration<JarEntry> entries = jar.entries();
						// 一樣的進行循環迭代
						while (entries.hasMoreElements()) {
							// 獲取jar裏的一個實體 能夠是目錄 和一些jar包裏的其餘文件 如META-INF等文件
							JarEntry entry = entries.nextElement();
							String name = entry.getName();
							// 若是是以/開頭的
							if (name.charAt(0) == '/') {
								// 獲取後面的字符串
								name = name.substring(1);
							}
							// 若是前半部分和定義的包名相同
							if (name.startsWith(packageDirName)) {
								int idx = name.lastIndexOf('/');
								// 若是以"/"結尾 是一個包
								if (idx != -1) {
									// 獲取包名 把"/"替換成"."
									packageName = name.substring(0, idx).replace('/', '.');
								}
								// 若是能夠迭代下去 而且是一個包
								if ((idx != -1) || recursive) {
									// 若是是一個.class文件 並且不是目錄
									if (name.endsWith(".class") && !entry.isDirectory()) {
										// 去掉後面的".class" 獲取真正的類名
										String className = name.substring(packageName.length() + 1, name.length() - 6);
										try {
											// 添加到classes
											classes.add(Class.forName(packageName + '.' + className));
										} catch (ClassNotFoundException e) {
											e.printStackTrace();
										}
									}
								}
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return classes;
	}

	/** * 以文件的形式來獲取包下的全部Class * * @param packageName * @param packagePath * @param recursive * @param classes */
	public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes) {
		// 獲取此包的目錄 創建一個File
		File dir = new File(packagePath);
		// 若是不存在或者 也不是目錄就直接返回
		if (!dir.exists() || !dir.isDirectory()) {
			return;
		}
		// 若是存在 就獲取包下的全部文件 包括目錄
		File[] dirfiles = dir.listFiles(new FileFilter() {
			// 自定義過濾規則 若是能夠循環(包含子目錄) 或則是以.class結尾的文件(編譯好的java類文件)
			public boolean accept(File file) {
				return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
			}
		});
		// 循環全部文件
		for (File file : dirfiles) {
			// 若是是目錄 則繼續掃描
			if (file.isDirectory()) {
				findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
						classes);
			} else {
				// 若是是java類文件 去掉後面的.class 只留下類名
				String className = file.getName().substring(0, file.getName().length() - 6);
				try {
					// 添加到集合中去
					classes.add(Class.forName(packageName + '.' + className));
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
複製代碼

基礎組件

Service

package cn.tuhu.springioc.service.impl;

import cn.tuhu.springioc.annotation.CustomComponent;
import cn.tuhu.springioc.service.UserService;

@CustomComponent
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("UserServiceImp: insert user");
    }
}
複製代碼

Controller

package cn.tuhu.springioc.controller;

import cn.tuhu.springioc.annotation.CustomComponent;
import cn.tuhu.springioc.annotation.CustomResource;
import cn.tuhu.springioc.service.UserService;

@CustomComponent
public class UserController {

    @CustomResource
    private UserService userServiceImpl;

    public void add() {
        System.out.println("UserController: receive request for add user");
        userServiceImpl.add();
    }
}
複製代碼

測試

package cn.tuhu.springioc.annotation;

import cn.tuhu.springioc.controller.UserController;
import cn.tuhu.springioc.ioc.CustomAnnotationApplicationContext;
import org.junit.Test;

public class UserControllerTest {

    @Test
    public void add() {
        CustomAnnotationApplicationContext context = new CustomAnnotationApplicationContext("cn.tuhu.springioc");
        UserController userController = (UserController) context.getBean("userController");
        userController.add();
    }
}

UserController: receive request for add user
UserServiceImp: insert user
複製代碼
相關文章
相關標籤/搜索