目錄
- 分層解耦
- 案例:將 emp.xml 中的數據解析并響應
- 三層架構
- 分層解耦
- IOC & DI 入門
- IOC 詳解
- DI 詳解
分層解耦
案例:將 emp.xml 中的數據解析并響應
emp.xml 內容如下:
<emps><emp><name>Tom</name><age>18</age><gender>1</gender><job>1</job></emp><emp><name>Jerry</name><age>19</age><gender>1</gender><job>2</job></emp><emp><name>Mike</name><age>20</age><gender>1</gender><job>3</job></emp><emp><name>Lucy</name><age>21</age><gender>2</gender><job>3</job></emp>
</emps>
引入相關依賴:
<dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version>
</dependency>
XML 文件解析工具類 XmlParserUtils:
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;public class XmlParserUtils {public static <T> List<T> parse(String file, Class<T> targetClass){ArrayList<T> list = new ArrayList<>();try {SAXReader saxReader = new SAXReader();Document document = saxReader.read(new File(file));Element rootElement = document.getRootElement();List<Element> elements = rootElement.elements("emp");for (Element element : elements) {String name = element.element("name").getText();String age = element.element("age").getText();String gender = element.element("gender").getText();String job = element.element("job").getText();Constructor<T> constructor = targetClass.getDeclaredConstructor(String.class, Integer.class, String.class, String.class);constructor.setAccessible(true);T object = constructor.newInstance(name, Integer.parseInt(age), gender, job);list.add(object);}} catch (Exception e) {e.printStackTrace();}return list;}
}
實體類 Emp:
public class Emp {private String name;private Integer age;private String gender;private String job;public Emp(String name, Integer age, String gender, String job) {this.name = name;this.age = age;this.gender = gender;this.job = job;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public String getJob() {return job;}public void setJob(String job) {this.job = job;}@Overridepublic String toString() {return "Emp{" +"name='" + name + '\'' +", age=" + age +", gender='" + gender + '\'' +", job='" + job + '\'' +'}';}
}
統一響應結果封裝類 Result:
/** 統一響應結果封裝類*/
public class Result {private Integer code; // 狀態碼,200為成功,500為失敗private String message; // 返回消息private Object data; // 返回數據public Result(Integer code, String message, Object data) {this.code = code;this.message = message;this.data = data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static Result success(Object data) {return new Result(200, "success", data);}public static Result success() {return new Result(200, "success", null);}public static Result error(String message) {return new Result(500, message, null);}@Overridepublic String toString() {return "Result{" +"code=" + code +", message='" + message + '\'' +", data=" + data +'}';}
}
請求處理類 EmpController:
import com.example.demo.pojo.Emp;
import com.example.demo.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;@RestController
public class EmpController {@RequestMapping("/listEmp")public Result list() {// 1. 加載并解析emp.xmlString file = this.getClass().getClassLoader().getResource("emp.xml").getFile();List<Emp> empList = XmlParserUtils.<Emp>parse(file, Emp.class);// 2. 對數據進行轉換處理empList.stream().forEach(emp -> {String gender = emp.getGender();if ("1".equals(gender)) {emp.setGender("男");} else if ("2".equals(gender)) {emp.setGender("女");}String job = emp.getJob();if ("1".equals(job)) {emp.setJob("講師");} else if ("2".equals(job)){emp.setJob("班主任");} else if ("3".equals(job)){emp.setJob("就業指導");}});//3. 響應數據return Result.success(empList);}
}
響應結果為:
三層架構
controller:控制層,接收前端發送的請求,對請求進行處理,并響應數據
service:業務邏輯層,處理具體的業務邏輯
dao:數據訪問層(Data Access Object,持久層),負責數據訪問操作,包括數據的增刪改查
回顧上述案例,會發現請求控制、業務邏輯處理和數據訪問都在同一個類中,這樣就會使代碼的復用性差以及難以維護,所以按照三層架構改為以下內容:
-
dao 層接口 EmpDao:
import com.example.demo.pojo.Emp; import java.util.List;public interface EmpDao {// 獲取員工列表數據public List<Emp> listEmp(); }
-
dao 層實現類 EmpDaoA:
import com.example.demo.dao.EmpDao; import com.example.demo.pojo.Emp; import com.example.demo.utils.XmlParserUtils; import java.util.List;public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {// 1. 加載并解析emp.xmlString file = this.getClass().getClassLoader().getResource("emp.xml").getFile();List<Emp> empList = XmlParserUtils.<Emp>parse(file, Emp.class);return empList;} }
-
service 層接口 EmpService:
import com.example.demo.pojo.Emp; import java.util.List;public interface EmpService {public List<Emp> listEmp(); }
-
service 層實現類 EmpServiceA:
import com.example.demo.dao.EmpDao; import com.example.demo.dao.impl.EmpDaoA; import com.example.demo.pojo.Emp; import com.example.demo.service.EmpService; import java.util.List;public class EmpServiceA implements EmpService {private EmpDao empDao = new EmpDaoA();@Overridepublic List<Emp> listEmp() {// 1. 調用dao層方法獲取數據List<Emp> empList = empDao.listEmp();// 2. 對數據進行轉換處理empList.stream().forEach(emp -> {String gender = emp.getGender();if ("1".equals(gender)) {emp.setGender("男");} else if ("2".equals(gender)) {emp.setGender("女");}String job = emp.getJob();if ("1".equals(job)) {emp.setJob("講師");} else if ("2".equals(job)){emp.setJob("班主任");} else if ("3".equals(job)){emp.setJob("就業指導");}});return empList;} }
-
controller 層實現類 EmpController:
import com.example.demo.pojo.Emp; import com.example.demo.service.EmpService; import com.example.demo.service.impl.EmpServiceA; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List;@RestController public class EmpController {private EmpService empService = new EmpServiceA();@RequestMapping("/listEmp")public Result list() {List<Emp> empList = empService.listEmp();//3. 響應數據return Result.success(empList);} }
這樣就完成了三層架構的分層,便于后期的管理與維護。
分層解耦
內聚:指一個模塊(類、方法、函數等)內部各個元素(如方法、屬性)之間的關聯緊密程度。內聚度越高,說明模塊內部的功能越集中、單一,“做一件事并做好”;內聚度低則意味著模塊功能混亂,職責不清晰。
耦合:指不同模塊之間的依賴緊密程度。耦合度越低,模塊之間的獨立性越強,一個模塊的修改對其他模塊的影響越小;耦合度高則意味著模塊 “牽一發而動全身”,難以維護。
按照案例中三層架構的流程,controller 中要調用 service 都需要創建一個 service 層的對象,controller 層與 service 層耦合;service 中要調用 dao 都需要創建一個 dao 層的對象,service 層與 dao 層耦合。若后續需要更換 EmpService 的實現類(比如新增 EmpServiceB),必須修改 EmpController 的代碼;當 Dao 層實現變更(如換成 EmpDaoB),得改動 EmpServiceA 代碼,耦合度高,不利于代碼擴展和維護。
為了解決這個問題,就要引入三個概念:控制反轉、依賴注入、Bean 對象:
- 控制反轉:Inversion Of Control,簡稱 IOC,對象的創建控制權由程序自身轉移到外部(容器),這種思想成為控制反轉。
- 依賴注入:Dependency Injection,簡稱 DI,容器為應用程序提供運行時所依賴的資源,稱為依賴注入。
- Bean 對象:IOC 容器中創建、管理的對象,稱為 Bean。
IOC & DI 入門
操作步驟:
- service 層及 dao 層的實現類,交給 IOC 容器管理,使用 @Component 注解
- 為 controller 層及 service 層注入運行時依賴的對象,使用 @Autowired 注解
- 運行測試
代碼做以下修改:
-
controller 層
import com.example.demo.pojo.Emp; import com.example.demo.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List;@RestController public class EmpController {@Autowiredprivate EmpService empService;@RequestMapping("/listEmp")public Result list() {List<Emp> empList = empService.listEmp();//3. 響應數據return Result.success(empList);} }
-
service 層
import com.example.demo.dao.EmpDao; import com.example.demo.pojo.Emp; import com.example.demo.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List;@Component public class EmpServiceA implements EmpService {@Autowiredprivate EmpDao empDao;@Overridepublic List<Emp> listEmp() {// 1. 調用dao層方法獲取數據List<Emp> empList = empDao.listEmp();// 2. 對數據進行轉換處理empList.stream().forEach(emp -> {String gender = emp.getGender();if ("1".equals(gender)) {emp.setGender("男");} else if ("2".equals(gender)) {emp.setGender("女");}String job = emp.getJob();if ("1".equals(job)) {emp.setJob("講師");} else if ("2".equals(job)){emp.setJob("班主任");} else if ("3".equals(job)){emp.setJob("就業指導");}});return empList;} }
-
dao 層
import com.example.demo.dao.EmpDao; import com.example.demo.pojo.Emp; import com.example.demo.utils.XmlParserUtils; import org.springframework.stereotype.Component; import java.util.List;@Component public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {// 1. 加載并解析emp.xmlString file = this.getClass().getClassLoader().getResource("emp.xml").getFile();List<Emp> empList = XmlParserUtils.<Emp>parse(file, Emp.class);return empList;} }
這樣就完成了模塊間的解耦操作
IOC 詳解
Bean 的聲明
要把某個對象交給 IOC 容器管理,需要在對應的類上加上如下注解之一:
注解 | 說明 | 位置 |
---|---|---|
@Component | 聲明 Bean 的基礎注解 | 不屬于以下三類時,用此注解 |
@Controller | @Component 的衍生注解 | 標注在控制器類上(@RestController 已包含) |
@Service | @Component 的衍生注解 | 標注在業務類上 |
@Repository | @Component 的衍生注解 | 標注在數據訪問類上(由于與 Mybatis 整合,用的少) |
聲明控制器 Bean 只能用 @Controller
在 IOC 容器中,每個 Bean 都有唯一標識,就是類名首字母小寫
當然也可以自己定義 Bean 的名稱,只需要在注解后面加上 value
@Repository(value = "daoA") // value也可以不寫
public class EmpDaoA implements EmpDao {//...
}
添加之后,Bean 的名稱就變為自己定義的
Bean 的組件掃描:
- 前面聲明 Bean 的四大注解,若要想生效,需要被組件掃描注解 @ComponentScan 掃描
- @ComponentScan 注解雖然沒有顯示配置,但實際上已包含在啟動類聲明注解 @SpringBootApplication 中,默認掃描的范圍是啟動類所在包及其子包
DI 詳解
@Autowired 注解,默認是按照類型進行,如果存在多個相同類型的 Bean,將會報出如下錯誤:
Field empService in com.itheima.controller.EmpController required a single bean, but 2 were found:- empServiceA: defined in file [E:\springboot - web - req - resp\target\classes\com\itheima\service\impl\EmpServiceA.class- empServiceB: defined in file [E:\springboot - web - req - resp\target\classes\com\itheima\service\impl\EmpServiceB.classAction:Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean
可以通過以下幾個方案來解決:
-
@Primary:如果有多個相同類型的 Bean,@Primary 加在哪個 Bean 上,哪個 Bean 就生效
@Primary @Service public class EmpServiceA implements EmpService {@Autowiredprivate EmpDao empDao;@Overridepublic List<Emp> listEmp() {//...} }
-
@Qualifier:放在 Bean 內,與 @Autowired 配合使用,@Qualifier(“Bean 的名稱”)
@Service public class EmpServiceA implements EmpService {@Qualifier("daoA")@Autowiredprivate EmpDao empDao;@Overridepublic List<Emp> listEmp() {// ...} }
-
@Resource:用 @Resource(name = “Bean 的名稱”) 來替換 @Autowired
@Service public class EmpServiceA implements EmpService {@Resource(name = "daoA")private EmpDao empDao;@Overridepublic List<Emp> listEmp() {// ...} }
@Resource 和 @Autowired 的區別:
- @Autowired 是 Spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
- @Autowired 是默認按照類型注入,而 @Resource 是默認按照名稱注入