手写springioc注解注入对象基本实现
生活随笔
收集整理的這篇文章主要介紹了
手写springioc注解注入对象基本实现
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
還是要養成寫注釋的習慣,首先一個代碼想讓人家看懂的情況下,記住一點,重構加設計模式,其實設計模式也比較好,達到別人可易讀性,這是我要跟你講的,而且你們不寫注釋是一個不好的習慣,你們一定要養成我善于重構代碼,善于寫注釋,把這樣的思想學會的情況下,我相信你以后是一個非常好的程序員,所以不要說別人不寫注釋的程序員就很牛,這不一定的,這個注解方式要怎么實現,在這個地方我們來看一段代碼,手寫注解版本的AOP容器,只要我加上一個注解,就會把這個類注入到Spring容器里面去,你們想想這種方式是怎么實現的,為什么加一個注解就能夠把這個類注入到Spring容器里面去呢,誰能告訴我答案的,思路是什么,我只要思路不要代碼,這個還真不是AOP,絕對不是AOP,注解AOP是經過方法的,我是在類上面加上注解之后,把我這個類注入到Spring容器里面去,是不是這樣的,其實我告訴你,怎么實現的呢,其實挺簡單的,真的挺簡單的,不是AOP,就是反射機制,我們畫圖,我們先說個思路,如果讓你們自己去寫Spring框架,你們怎么去寫,你最好答出來,因為我是真的寫過的,我不是光看源碼那么簡單的,所以這個都是比較重要的,這是我講注解版本的實現,我們把依賴信息給copy過來,不用AOPhttps://cloud.tencent.com/developer/article/1424714手寫源碼(二):自己實現SpringIOC
https://www.jianshu.com/p/274275cf28ce
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 這個是掃包范圍 這段代碼表示掃包注解的掃包范圍,它是先讀取配置文件,把包名讀到之后,讀到他包下有哪些子類,那些個類,然后再獲取這個類上有沒有這個注解,有注解的情況下再利用反射機制,幫你去初始化了,是不是這樣的,--><context:component-scan base-package="com.learn"></context:component-scan></beans>
package com.learn.service;//user 服務層
public interface UserService {public void add();public void del();
}
package com.learn.service.impl;import org.springframework.transaction.annotation.Transactional;import com.learn.annotation.ExtService;
import com.learn.service.UserService;//user 服務層
/*** @ExtService表示要注入到Spring容器里面去* * @author Leon.Sun**/
@ExtService
public class UserServiceImpl implements UserService {@Transactionalpublic void add() {System.out.println("使用JAVA反射機制初始化對象....");}// 方法執行完畢之后,才會提交事務public void del() {}}
package com.learn.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;// 自定義注解 注入到Spring容器
/*** 自定義注解 service 注入bean容器* @author Leon.Sun**/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtService {}
package com.learn.utils;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;/*** 這段代碼千萬不要去寫* 寫了沒意義* 這個我大體說一下原理* 原理是什么樣的呢* * * @author Leon.Sun**/
public class ClassUtil {/*** 取得某個接口下所有實現這個接口的類*/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('.', '/');// 定義一個枚舉的集合 并進行循環來處理這個目錄下的thingsEnumeration<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包文件// 定義一個JarFileJarFile jar;try {// 獲取jarjar = ((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 {// 添加到classesclasses.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) {// 獲取此包的目錄 建立一個FileFile 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();}}}}
}
package com.learn.spring;import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;import org.apache.commons.lang.StringUtils;import com.learn.annotation.ExtService;
import com.learn.utils.ClassUtil;/*** 手寫Spring IOC 注解版本* 我就不寫入到xml里面去* 我就傳入進來* * * @author Leon.Sun**/
public class ExtClassPathXmlApplicationContext {// 包名private String packageName;// bean容器/*** 我們定義一個集合ConcurrentHashMap* 這個String表示bean的id* 你們覺得這里存Object好呢還是存對象好呢* 是存Class地址好呢還是存Object好* 你們想想* 其實這個看你們自己* 我覺得存地址相對靈活點* 那么Object相對好一點* 就是他不用在初始化了* 是不是這樣的* 地址稍微好一點* 擴展性高一點* 萬一他想要class地址* 地址他可以隨便new的* Class地址是最好的* Spring里面其實也是這樣寫的* 靜態其實最好的* 沒任何影響的* 但是要記住一點* 線程安全問題* 但是ConcurrentHashMap線程安不安全* 您們知不知道* 肯定是安全的* 源碼里面好像是靜態的* 源碼里面我也忘記了* 你們也可以把它作為靜態的* 這個有很多實現方式* 歸根結底* 你們也可以在這里初始化* 但是告訴你們* 但是你們最好不要在上面直接初始化* 這個我要告訴你們的* 怎么做的呢* 先把他置為null* 有的人會問* 那我什么時候去初始化呢* 記住一點* 我待會告訴你們* 你們真正寫的情況下* 有個方法* 加載的時候才初始化的* 不要一上去就初始化* 我們先不初始化* 我后面講什么時候初始化* * Spring bean容器* */private ConcurrentHashMap<String, Object> beans = null;/*** 構造函數** @param packageName 包名* @throws InstantiationException* @throws IllegalAccessException*/public ExtClassPathXmlApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {/*** 你們最好判斷這個地址是否為空的*/this.packageName = packageName;beans = new ConcurrentHashMap<String, Object>();initBeans();}/*** 初始化對象* 你們知道怎么去獲取當前包下所有的類* 怎么去獲取* 方法是什么* 其實原理比較簡單* 讀取有哪些class名稱* 比如讀取com.learn.service* 知道他下面有哪些類* 就是這樣的* 這個大家直接用API就行了* 反射工具類* 這也是我從網上找的* * 初始化bean我們放在什么地方* 一般都是放在構造函數初始化* * ** @throws IllegalAccessException* @throws InstantiationException*/private void initBeans() throws IllegalAccessException, InstantiationException {// 遍歷所有類/*** 這里傳入我們的包名packageName* 是不是返回所有的class地址* 我們這一步拿到了* * 首先讀取這個包有哪些類* */List<Class<?>> classes = ClassUtil.getClasses(packageName);// 將所有標注ExtService注解的類加入到容器中findClassExistAnnotation(classes);/*** 如果他等于null就報個錯出來* 你們可以報錯也可以不報錯* 正常情況不會報錯* 看你們自己* 我 為了演示效果就拋個異常出來* * */if (beans == null || beans.isEmpty()) {/*** 該包下沒有任何類加上注解*/throw new RuntimeException("沒有類加上了注解");}}/*** 過濾標注ExtService注解的類* 判斷類上面是否有注解呢* ** @param classes* @throws InstantiationException* @throws IllegalAccessException*/private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {/*** 這里是classInfo* */for (Class<?> classInfo : classes) {/*** 調用這個方法判斷是否有注解* 我傳入ExtService* 判斷類上是否有注解* * */Annotation annotation = classInfo.getAnnotation(ExtService.class);/*** 如果現在上面一旦有的話* 有的話怎么辦* 如果annotation他不等于null的話他會怎么做* * 有沒有注解* 因為我后面加了注解* * */if (annotation != null) {//到這里表示有這個注解/*** 有注解我就獲取他的類名* */String className = classInfo.getName();//默認Id是首字母小寫/*** 在這里就有一個問題* 在這里要存放的是bean的id* 這個beanid怎么獲取* 注解里面的beanid是怎么實現的* 想一想注解的beanid是怎么來的* beanid是默認類名小寫* 是不是這樣的過程* 在這邊我要說一下* 我來問你們* 判斷有沒有值* 如果他有傳入beanid名稱之后* 默認會用到beanid的名稱* 如果沒有傳就默認小寫* 這個看你自己* 你們如果想寫的非常完善的話* 你可以自己去寫* classInfo.getSimpleName()這個是獲取當前類名* 獲取完當前類名之后* 我們就可以拿到className* 正常情況下他會不會為空* 既然能被反射出來肯定是不會為空的* 如果你想寫的特別完善的話* 你判斷一下也可以* 其實沒必要判斷* 為什么呢* 這不為空的* 這樣的* 肯定是有值的* 這個我就說一下* 您們說在這里是continue好呢* 還是break好呢* 你們想一想* 肯定是continue* 因為還需要繼續掃包檢查* * 把類名變成小寫的* 存放到bean的容器里面去* * * */beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo));}}}/**類名的首字母小寫*//*** 把你的類名首字母變成小寫的* 我就不去說了* 這里我們可以拿到newClassName* 就是新的name名稱* 這里叫beanid也可以* * @param className* @return*/private String toLowerCaseFirestOne(String className) {return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString();}/**獲取Bean的方法*//*** 這里傳入一個beanId* * * @param beanId* @return* @throws IllegalAccessException* @throws InstantiationException*/public Object getBean(String beanId) throws IllegalAccessException, InstantiationException {/*** 你們只需要判斷beanid* * 是不是傳入beanid進來* * */if (StringUtils.isEmpty(beanId)) {/*** 如果拋異常表示beanid不能為空* 如果beanid參數不能為空* * */throw new RuntimeException("BeanID為空");}/*** 通過beanid去查找一個對象* 他就拿到對象了* 從Spring容器獲取bean* */return beans.get(beanId);}/**利用反射機制創建Bean*/private Object newInstance(Class<?> classInfo) throws IllegalAccessException, InstantiationException {/*** 如果他為空的情況下* 就說明什么情況* 是不是沒有找到bean* * */if (classInfo == null) {throw new RuntimeException("沒有這個ID的bean");}/*** 使用反射機制初始化對象* * 利用反射去初始化對象的* */return classInfo.newInstance();}/**依賴注入傳入類的屬性*/private void attrAssign(Class<?> classInfo) {//獲取這個類所有的屬性Field[] fields = classInfo.getFields();//判斷當前屬性是否有注解for (Field field : fields) {ExtService extService = field.getAnnotation(ExtService.class);if (extService != null) {//到這里說明這個屬性里有這個注解String fieldName = field.getName();}}}
}
package com.learn;import com.learn.service.UserService;
import com.learn.spring.ExtClassPathXmlApplicationContext;/*** 核心是三步* 但是代碼是比較麻煩的* 我可能會用到一些工具類* 那么我們這邊同樣道理* 我們首先要定義注解* * * @author Leon.Sun**/
public class Test001 {public static void main(String[] args) throws Exception {/*** 在使用注解版本事務的時候* 你們第一步要做什么* 怎么樣保證我的注解一定要生效呢* 我要怎么做* 想一想* 記住掃包* 為什么要加掃包* 注解定義完了不一定能用它* 是不是要加掃包* 你們之前為什么要在配置文件里面加這樣的一段代碼* 干嘛用的* 就是用來做掃包的操作* 我們就以這個為切入點* 怎么進行實現過來的* 那么我們在這里給你詳細講一下這個步驟* 跟著掃包去分析* 大家一定要跟著我的思路去想想* 首先要利用JAVA的反射機制掃包* 獲取當前包下所有的類* 是不是這樣的* 這個是不是第一步* 我肯定要獲取當前包下所有的類* * *//*** 想想第二步干嘛* 寫白話文* 判斷類上是否存在注入bean的注解* * *//*** 如果在類上加上注解的話* 我要怎么做* 使用JAVA的反射機制進行初始化* 簡單來說就是這三步* 但是我告訴你絕對不止這三步* 比這個難多了* 這只是最簡單的三步* 我們細講一下底層到底是怎么實現的* 所有有人的人說用AOP* AOP確實可以實現* 但是AOP太麻煩了* AOP我們一般用哪個方法我們用AOP* 但是在類上的話我們要用反射進行實現* 是不是這樣的* 那么這個思路大家有沒有一點頭緒* * *//*** 我們這個要導包* com.learn.service.impl它是我們需要加載的* 我們在我們的Service上加上我們的注解@ExtService* 我們要傳UserServiceImpl類名首字母小寫的* 如果我不加注解它是肯定找不到的* UserServiceImpl類上面我們是沒有加注解的* * */ExtClassPathXmlApplicationContext app = new ExtClassPathXmlApplicationContext("com.learn.service.impl");UserService userService = (UserService) app.getBean("userServiceImpl");/*** 打印一下這個效果*/System.out.println(userService);}}
?
總結
以上是生活随笔為你收集整理的手写springioc注解注入对象基本实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手写springiocxml方式注入对象
- 下一篇: 分布式Session一致性概述