前言:本人大四學生,第一次寫博客,如果有寫得不好的地方,請大家多多指正
一、IoC(Inversion of Control)反轉控制
傳統開發都是需要對象就new,但這樣做有幾個問題:
-
- 效率低下,創建對象時比較耗時,我立馬要用對象,可系統說讓你等一下,創建和初始化對象需要一定的時間。
- 對象關聯關系,例如:用戶有所屬部門,那創建用戶對象時,如果部門對象不存在,還得創建部門對象。
- 代碼耦合度較高
於是有人就提出了IoC控制反轉概念,干嘛我不先創建好呢?如果用戶要使用時,我都已經創建好了,用戶不就能立馬使用了?這就有點像好萊塢法則:“你別找我,我去找你”。
IoC不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器后,把創建和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,所以對象與對象之間是松散耦合,這樣也方便測試,利於功能復用,更重要的是使得程序的整個體系結構變得非常靈活;
我個人對IoC是這樣簡單理解的:
1)對象不是new出來的,而是通過反射事先創建出來的
2)既然事先創建出來,在用之前就得找個地方放起來,放哪里呢?就放在一個Map<String, Object>集合中
二、實現IoC過程中需要考慮的一些問題
• 怎么獲取要創建的對象?
• 怎么把目錄名轉換為類名?
• 怎么把類名轉換為beanName?
• 哪些類需要創建對象?怎么過濾掉不需要創建對象的類?
• 容器中該怎么存放對象?
• 怎么調用容器中存放的對象?
• 設計分層結構:高內聚,低耦合(相當於專做一件事,不要把使用功能都寫在一起)
三、模擬Spring實現IoC
在這里簡單模擬查詢用戶信息,實際應用中更復雜,但是萬變不離其宗,弄懂原理才是重要的
1)架構圖
2)代碼結構圖
在IDEA中的結構示意圖(eclipse也差不多,我用的是IDEA):
各包名以及文件名含義(文件名可能會和架構圖中的一些文件名不一致,因為架構圖是之前畫的,但不影響閱讀,大同小異):
annotation包:用來存放注解,在本案例中需要創建對象的類為控制層類、業務層類、持久層類;所有需要3個注解,通過注解的方式來過濾掉不需要創建對象的類,Controller注解是加在控制層,Service注解加在業務層,Respository注解加在持久層;3個注解都是位於類上,保留到運行時期;
pojo包:設置一個pojo類,里面含有一些簡單屬性;
dao包:模擬持久層,由於本案例是一個簡單例子,主要用於初學者查看,所有沒有安裝數據庫,正常情況下持久層應該與數據庫進行對接,沒有安裝數據庫,所有就由持久層模擬數據庫;
service包:模擬業務層,業務層調用持久層;
controller包:模擬控制層,控制層調用業務層;
utils包:工具類包;
parse包:因為我們采用了分層結構,所以在使用之前,應當把這些組件掃描加載到一起;現在都是面向接口進行開發,這樣可以提高程序的靈活性,面向接口開發,同時也體現了JAVA的多態性;
Run文件就相當於整個程序的入口。
四、案例源碼
1)pojo類(User.java)
1 package spring.pojo; 2 3 /** 4 * @Auther: 林安傑 5 * @Date: 2019/9/28 19:28 6 * @Description:創建pojo對象,簡單設置屬性以及get,set,toString方法 7 */ 8 public class User { 9 private Integer id; 10 private String name; 11 12 public Integer getId() { 13 return id; 14 } 15 16 public void setId(Integer id) { 17 this.id = id; 18 } 19 20 public String getName() { 21 return name; 22 } 23 24 public void setName(String name) { 25 this.name = name; 26 } 27 28 @Override 29 public String toString() { 30 return "User{" + 31 "id=" + id + 32 ", name='" + name + '\'' + 33 '}'; 34 } 35 }
2)注解
①Respository.java
1 package spring.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 /** 9 * @Auther: 林安傑 10 * @Date: 2019/9/28 19:31 11 * @Description:dao層類注解 12 */ 13 14 @Target(ElementType.TYPE) // 注解加在類上 15 @Retention(RetentionPolicy.RUNTIME) // 保留到運行時 16 public @interface Respository { 17 }
②Service.java
1 package spring.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 /** 9 * @Auther: 林安傑 10 * @Date: 2019/9/28 19:33 11 * @Description:業務層注解 12 */ 13 14 @Target(ElementType.TYPE) 15 @Retention(RetentionPolicy.RUNTIME) 16 public @interface Service { 17 }
③Controller.java
1 package spring.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 /** 9 * @Auther: 林安傑 10 * @Date: 2019/9/28 19:34 11 * @Description:控制層注解 12 */ 13 14 @Target(ElementType.TYPE) 15 @Retention(RetentionPolicy.RUNTIME) 16 public @interface Controller { 17 }
3)持久層(UserDao.java)
1 package spring.dao; 2 3 import spring.annotation.Respository; 4 import spring.pojo.User; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 9 /** 10 * @Auther: 林安傑 11 * @Date: 2019/9/28 19:36 12 * @Description:dao層,模擬數據庫數據 13 */ 14 15 @Respository 16 public class UserDao { 17 // 模擬查詢一條記錄,根據進行id查詢 18 public User selectOne(Integer id) { 19 User user = new User(); 20 user.setId(id); 21 user.setName("林安傑"); 22 return user; 23 } 24 25 // 模擬查詢所有記錄 26 public List<User> get() { 27 // 用一個集合來存放所有對象 28 List<User> userList = new ArrayList<>(); 29 30 User u1 = new User(); 31 u1.setId(6); 32 u1.setName("linanjie"); 33 userList.add(u1); 34 35 User u2 = new User(); 36 u2.setId(7); 37 u2.setName("linlin"); 38 userList.add(u2); 39 return userList; 40 } 41 42 43 }
4)業務層(UserService.java)
1 package spring.service; 2 3 import spring.annotation.Service; 4 import spring.dao.UserDao; 5 import spring.pojo.User; 6 7 import java.util.List; 8 9 /** 10 * @Auther: 林安傑 11 * @Date: 2019/9/28 19:48 12 * @Description:業務層,從dao層中獲取數據 13 */ 14 15 @Service 16 public class UserService { 17 private UserDao userDao; // 把dao層的對象設置為私有成員變量,才能訪問dao層中的方法(組合) 18 19 // 關系綁定(set注入) 20 public void setUserDao(UserDao userDao) { 21 this.userDao = userDao; 22 } 23 24 // 查詢一條記錄 25 public User getOne(Integer id) { 26 return userDao.selectOne(id); 27 } 28 29 // 查詢所有記錄 30 public List<User> queryAll() { 31 return userDao.get(); 32 } 33 }
5)控制層(UserController.java)
1 package spring.controller; 2 3 import spring.annotation.Controller; 4 import spring.pojo.User; 5 import spring.service.UserService; 6 7 import java.util.List; 8 9 /** 10 * @Auther: 林安傑 11 * @Date: 2019/9/28 19:53 12 * @Description:控制層,調用業務層 13 */ 14 15 @Controller 16 public class UserController { 17 private UserService userService; // 把業務層對象設置為私有成員變量 18 19 // set注入,實現關系綁定 20 public void setUserService(UserService userService) { 21 this.userService = userService; 22 } 23 24 // 查詢一條記錄 25 public User getOne(Integer id) { 26 return userService.getOne(id); 27 } 28 29 // 查詢多條記錄 30 public List<User> queryAll() { 31 return userService.queryAll(); 32 } 33 }
6)工具類
①FileUtil.java
1 package spring.utils; 2 3 import java.io.File; 4 import java.util.List; 5 6 /** 7 * @Auther: 林安傑 8 * @Date: 2019/9/28 19:58 9 * @Description:文件掃描工具類 10 */ 11 12 public class FileUtil { 13 // 掃描所有.class文件,並存入數組中 14 public static List<String> getFileNameList(String dir, List<String> fileNameList) { 15 File file = new File(dir); // 把string類型轉換為file類型 16 File[] files = file.listFiles(); // 遍歷當前路徑下的所有文件(遍歷出來的是:所有文件名+所有目錄名) 17 for (File f : 18 files) { 19 if (f.isDirectory()) { // 判斷當前文件是不是目錄,是的話就遞歸遍歷目錄中的內容 20 getFileNameList(f.getAbsolutePath(), fileNameList); 21 } else { 22 fileNameList.add(f.getAbsolutePath()); // 不是目錄就是文件,將文件添加到數組當中 23 } 24 } 25 return fileNameList; 26 } 27 }
②CoverUtil.java
1 package spring.utils; 2 3 import java.util.List; 4 5 /** 6 * @Auther: 林安傑 7 * @Date: 2019/9/28 21:30 8 * @Description:統一把路徑符替換為“/”,路徑符有“//”、“\”,統一后就沒有差異性了 9 */ 10 public class CoverUtil { 11 // 從存放所有class文件的fileNameList集合中取出每一個文件名(全局限定名),替換為類名形式(包名.類名) 12 13 /** 14 * @param baseDir 基准路徑 15 * @param fileNameList 存放所有文件全集限定名的數組 16 * @return 17 */ 18 public static List<String> getClassNameList(String baseDir, List<String> fileNameList) { 19 // 把基准路徑中的路徑符統一 20 baseDir = baseDir.replace("\\", "/"); 21 for (int i = 0; i < fileNameList.size(); i++) { 22 // 獲取數組中的元素 23 String fileName = fileNameList.get(i); 24 25 // 把元素(全集限定名)中的路徑統一 26 fileName = fileName.replace("\\", "/"); 27 28 // 把全集限定名中前面一樣的基准路徑替換為空串 29 fileName = fileName.replace(baseDir, ""); 30 31 // 把替換掉的字符串去掉.class后綴 32 int pos = fileName.lastIndexOf("."); 33 fileName = fileName.substring(0, pos); 34 35 // 把字符串的路徑符替換為“.” 36 fileName = fileName.replace("/", "."); 37 38 // 把最終的類名繼續放回數組中 39 fileNameList.set(i, fileName); 40 } 41 return fileNameList; 42 } 43 }
③BeanNameUtil.java
1 package spring.utils; 2 3 4 /** 5 * @Auther: 林安傑 6 * @Date: 2019/9/28 23:06 7 * @Description:把className轉換為beanName 8 */ 9 public class BeanNameUtil { 10 public static String getbeanName(String className) { 11 // 截取后面的類名 12 int pos = className.lastIndexOf(".") + 1; 13 className = className.substring(pos); 14 // 類名的第一個字母小寫,后面的不變 15 String beanName = className.toLowerCase().charAt(0) + className.substring(1); 16 return beanName; 17 } 18 }
7)組件掃描
①BeanFactory.java
1 package spring.parse; 2 3 import java.util.List; 4 5 /** 6 * @Auther: 林安傑 7 * @Date: 2019/9/28 23:26 8 * @Description:面對接口開發;需要設計一個接口 9 */ 10 public interface BeanFactory { 11 public List<String> scan(String dir); 12 13 public void creat(String baseDir, List<String> fileNameList) throws Exception; 14 15 public <T> T getObject(String beanName); 16 }
②ComponentScan.java
1 package spring.parse; 2 3 import spring.annotation.Controller; 4 import spring.annotation.Respository; 5 import spring.annotation.Service; 6 import spring.utils.BeanNameUtil; 7 import spring.utils.CoverUtil; 8 import spring.utils.FileUtil; 9 10 import java.util.ArrayList; 11 import java.util.HashMap; 12 import java.util.List; 13 import java.util.Map; 14 15 /** 16 * @Auther: 林安傑 17 * @Date: 2019/9/28 22:30 18 * @Description:掃描所有需要的組件,並利用反射創建對象,把對象放在容器中 19 */ 20 public class ComponentScan implements BeanFactory{ 21 22 private static final Map<String, Object> beans = new HashMap<>(); 23 24 // 掃描所有class文件 25 public List<String> scan(String dir) { 26 List<String> fileNameList = new ArrayList<>(); 27 FileUtil.getFileNameList(dir, fileNameList); 28 // 遍歷查看數組的內容 29 for (String fileName : 30 fileNameList) { 31 System.out.println(fileName); 32 } 33 return fileNameList; 34 } 35 36 // 通過反射根據注解創建對象,並把對象放在容器中 37 public void creat(String baseDir, List<String> fileNameList) throws Exception { 38 List<String> classNameList = CoverUtil.getClassNameList(baseDir, fileNameList); 39 40 // 遍歷類名數組查看內容,並根據是否有注解創建對象 41 for (String className : classNameList) { 42 String beanName = ""; 43 Class<?> clazz = Class.forName(className); 44 // 獲取類上的注解 45 Controller ca = clazz.getAnnotation(Controller.class); 46 Service sa = clazz.getAnnotation(Service.class); 47 Respository ra = clazz.getAnnotation(Respository.class); 48 49 // 若注解不為空,就創建對象 50 if (ca != null || sa != null || ra != null) { 51 Object obj = clazz.newInstance(); 52 53 /*把對象存放在容器中,容器為Map鍵值對,值就是對象,key用className表示太復雜,把className轉換成beanName 54 className的形式:spring.pojo.User;beanName的形式:user;所以需要寫一個轉換的方法 55 */ 56 beanName = BeanNameUtil.getbeanName(className); 57 58 // 把對象存放到容器當中 59 beans.put(beanName, obj); 60 } 61 System.out.println(className + " | " + beanName); 62 } 63 64 // 查看容器中存放的對象 65 System.out.println("\n容器中存放的對象為:"); 66 for (String key : beans.keySet()) { 67 System.out.println(beans.get(key)); 68 } 69 } 70 71 // 從容器中獲取對象 72 public <T> T getObject(String beanName){ 73 return (T) beans.get(beanName); 74 } 75 }
8)程序入口(Run.java)
1 package spring; 2 3 import spring.controller.UserController; 4 import spring.dao.UserDao; 5 import spring.parse.BeanFactory; 6 import spring.parse.ComponentScan; 7 import spring.pojo.User; 8 import spring.service.UserService; 9 10 import java.util.List; 11 12 13 /** 14 * @Auther: 林安傑 15 * @Date: 2019/9/29 12:53 16 * @Description:程序入口 17 */ 18 public class Run { 19 public static void main(String[] args) throws Exception { 20 // 面對接口開發,體現了多態性(向上造型) 21 BeanFactory context = new ComponentScan(); 22 23 // 先掃描所有class文件 24 System.out.println("第一步,掃描當前主目錄下的所有class文件:"); 25 // 不能把路徑寫死,可以根據API獲取路徑 26 String baseDir = Run.class.getResource("/").getPath().substring(1); 27 String packageDir = Run.class.getPackage().getName(); 28 String dir = baseDir + packageDir.replace(".", "/"); 29 List<String> fileNameList = context.scan(dir); 30 31 System.out.println("\n第二步,獲得className,並且用beanName把對象存入容器中:"); 32 context.creat(baseDir, fileNameList); 33 34 System.out.println("\n第三步,業務處理:"); 35 // 不能用new創建對象,需要從容器中獲取對象 36 UserController userController = context.getObject("userController"); 37 UserService userService = context.getObject("userService"); 38 UserDao userDao = context.getObject("userDao"); 39 40 // 關系注入,調用set方法 41 userController.setUserService(userService); 42 userService.setUserDao(userDao); 43 44 System.out.println("查詢一條記錄:"); 45 User user = userController.getOne(1); 46 System.out.println(user); 47 48 System.out.println("查詢所有記錄:"); 49 List<User> users = userController.queryAll(); 50 for (User u : users) { 51 System.out.println(u); 52 } 53 } 54 }
五、運行結果
這就是本期分享的內容,大家可以相互學習