100行程式碼擼完SpringIOC容器
阿新 • • 發佈:2018-12-15
用過Spring框架的人一定都知道Spring的依賴注入控制反轉;通俗的講就是負責例項化物件 和 管理物件間的依賴 實現解耦。
我們來對比兩段程式碼:
UserController{ UserService userService = new UserController(); userService.insert(user); } UserController{ @Autowwired UserService userService; userService.insert(user); }
乍一看好像沒什麼區別,好像都是一樣的。在controller裡面建立了一個service物件然後呼叫它裡面的方法。但是換個角度想想, 如果還有2個,3個,甚至n個類需要用到這個service呢,那它豈不是要被建立n次,這樣就會極大的浪費資源,分分鐘就記憶體溢位了。
企業開發案例:
我們只需要在xml配置檔案裡面指定配置引數,然後在類上加上spring註解它就能幫我們管理物件了,那它又是怎麼實現的呢? 答案是: xml+反射+工廠模式。
手寫IOC容器,演示它的載入過程:
1. 模仿spring,我們也定義一個註解
@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface MyService { } @Target({ TYPE, FIELD, METHOD }) @Retention(RUNTIME) public@interface MyAutowired { }
2. 像使用spring註解一樣,使用我們剛才的註解
@MyService public class OrderService { public void add() { System.out.println("Order....add()"); } } @MyService public class UserService { @MyAutowired private OrderService orderService; public void add() { orderService.add(); System.out.println("User....add()"); } }
3. 演示容器載入核心程式碼
/** * SpringIOC容器實現過程 */ public class MyClassPathXmlApplicationContext { // 1. 指定掃描包的範圍 private String packageName ; // Spring的bean容器 (容器啟動時初始化所有bean,預設單例) key:beanName(預設類名小寫) value:bean物件 private ConcurrentHashMap<String, Object> beans; public MyClassPathXmlApplicationContext(String packageName) throws Exception { // 類載入時初始化這些引數 this.packageName = packageName; this.beans = new ConcurrentHashMap<String, Object>(); initBeans();// 初始化所有類 initEntryField();// 初始化所有類的屬性 } /** * 初始化bean(IOC控制反轉) */ public void initBeans() throws Exception { // 2. 通過java反射掃描指定包下面所有類的class地址 List<Class<?>> classes = ClassUtil.getClasses(packageName); ConcurrentHashMap<String, Object> classExistAnnotation = this.findClassExistAnnotation(classes); if(classExistAnnotation == null || classExistAnnotation.isEmpty()) throw new Exception("cannot find bean"); } /** * 判斷類上面是否存在bean的註解 */ private ConcurrentHashMap<String, Object> findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException { for(Class<?> classInfo : classes) { // 3. 獲取指定路徑中有ioc註解的類 MyService annotation = classInfo.getAnnotation(MyService.class); if(annotation!=null) { String className = classInfo.getSimpleName();// 獲取類名稱 // 4. 將類名首字母小寫,獲取bean名稱。 String beanaName = StringUtil.toLowerCaseFirstOne(className); // 5. 反射初始化物件 Object object = classInfo.newInstance(); // 6. 將bean物件存入spring容器中 beans.put(beanaName, object); } } return beans;// 物件初始化完成 } /** * 初始化屬性 */ private void initEntryField() throws Exception { // 7. 遍歷spring容器中所有的bean物件 for (Entry<String, Object> entry : beans.entrySet()) { Object bean = entry.getValue(); this.attriAssign(bean); } } /** * 依賴注入實現原理(DI依賴注入) */ public void attriAssign(Object object) throws Exception { Class<? extends Object> classInfo = object.getClass(); // 8. 使用反射機制,獲取當前類的所有屬性值 Field[] fields = classInfo.getDeclaredFields(); for(Field field : fields) { // 9. 獲取有@MyAutowired註解的屬性 MyAutowired myAutowired = field.getAnnotation(MyAutowired.class); if(myAutowired != null) { // 10. 獲取屬性名稱 String beanName = field.getName(); // 11. 預設使用屬性名稱,查詢bean容器物件 Object bean = this.getBean(beanName); if(bean != null) { field.setAccessible(true);// 允許訪問私有屬性 // 12. 將得到的bean物件賦值給當前物件的屬性上。(bean名稱=屬性名稱) field.set(object, bean); } } } } /** * 通過bean名稱去spring容器裡面獲取bean物件 */ public Object getBean(String beanName) throws Exception { if(StringUtils.isEmpty(beanName)) throw new Exception("beans.factory.BeanCreationException"); Object object = beans.get(beanName); return object; } /** * 測試 */ public static void main(String[] args) throws Exception { MyClassPathXmlApplicationContext applicationContext = new MyClassPathXmlApplicationContext("com.wulei.service"); UserService userService = (UserService)applicationContext.getBean("userService"); userService.add(); } }
依賴注入:比如userController裡面的userService屬性加上bean註解,在類被載入時通過反射獲取service的物件,並且賦值給該屬性,這就叫做依賴注入。
控制反轉:controller引用service的例項,不需要通過new來建立,將建立物件的責任轉移給spring容器,這就叫反轉; ioc容器實現物件的建立,以及外部資源的獲取(其他類的屬性和方法),這就叫控制。