文章目錄
- 模塊分析
- 模塊
- 分析
- 描述五張表的關系
- 重要知識講解
- 抽取成一個BaseServlet
- SpringIOC思想(底層)
- 實現代碼
- IOC概述
- SPI機制(為學習框架做思想和技術鋪墊)
- SPI引入
- 1. 標準/規范
- 2. 具體的實現
- 3. 調用
- SPI介紹
- SPI練習JDBC4.0免注冊驅動原理
- Servlet實現方式三 ServletContainerInitializer
模塊分析
模塊
項目分為三個模塊
- 用戶模塊:完成增刪改查
- 角色模塊:增刪改查
- 權限模塊:增刪改查
分析
用戶和角色的關系:
一個用戶有多個角色,張三可以是SVIP,也可以是綠鉆
一個角色可以對應多個用戶,SVIP可以是張三,也可以是李四
結果:用戶和角色屬于多對多的關系,根據表設計原則,多對多關系創建中間表,在中間表起碼要有另外倆張主表用戶和角色的主鍵作為外鍵進行關聯
角色和權限的關系:
一個角色有多個權限,SVIP可以點贊20次,可以使qq名變紅
一個權限可以對應多個角色,使qq名變紅可以是SVIP,也可以是VIP
結果:角色和權限屬于多對多的關系,根據表設計原則,多對多關系創建中間表,在中間表起碼有另外倆張主表權限和角色的主鍵作為外鍵進行關聯
注:用戶 角色 權限具有經典的五張表
描述五張表的關系
重要知識講解
抽取成一個BaseServlet
問題:對用戶進行增刪改查,那么單對用戶就要寫4個Servlet,這樣創建的類太多比較麻煩
解決措施:我們想的是對于用戶只創建一個Servlet,路徑的話變成/user/*,這樣關于用戶的所有操作都會放到這個Servlet類中,在里面定義方法來分別操作用戶,方法名和路徑名保持相同,代碼看著簡單明了,很簡潔
問題:但是這樣做我們就只能使用在用戶模塊,不能使用其他模塊,造成代碼冗余,還得必須使用if判斷到底執行哪個方法
解決措施:前面已經學過可以獲取前端的請求數據,那么就可以獲取到請求路徑,然后用反射來執行方法,然后將其抽取到一個類中,讓其他模塊創建的Servlet類繼承這個類,其他模塊的Servlet類中只寫操作方法即可
關鍵:this關鍵字
代碼如下:
public class BaseServlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doGet(request, response);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//獲取請求路徑String uri = request.getRequestURI();int i = uri.lastIndexOf("/");String methodName = uri.substring(i + 1);//判斷方法名//名字固定,不利于整體書寫
// if ("findAll".equals(methodName)){
// findAll(request,response);
// } else if ("update".equals(methodName)) {
// update(request,response);
// } else if ("add".equals(methodName)) {
// add(request,response);
// }else if ("delete".equals(methodName)){
// delete(request,response);
// }//用反射技術,使用固定代碼執行所有方法//方法名和路徑相同,利用反射獲取方法名Class aClass = this.getClass();try {Method method = aClass.getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);method.invoke(this,request,response);} catch (Exception e) {throw new RuntimeException(e);}}
}
@WebServlet("/user/*")
public class UserServlet extends BaseServlet {private void delete(HttpServletRequest request, HttpServletResponse response) {System.out.println("刪除用戶");}public void add(HttpServletRequest request, HttpServletResponse response) {System.out.println("增加用戶");}public void update(HttpServletRequest request, HttpServletResponse response) {System.out.println("更新用戶");}public void findAll(HttpServletRequest request, HttpServletResponse response) {}
}
SpringIOC思想(底層)
實現代碼
問題:我們以前創建對象都是通過new創建對象,例如:創建業務層對象:
UserServiceImple userServiceImpl = new UserServiceImpl();
這種方式創建對象的弊端是:嚴重耦合。如果業務層實現類UserServiceImpl被刪除了,web會報錯。或者在實際開發中向對業務層進行擴展,一般是定義一個新的業務層類UserServiceImpl2,然后將之前的UserServiceImpl替換,就造成web層無法運行,那么太過于耦合
解決措施:降低耦合。就是不在web層中使用new方式創建對象
使用:反射+面向接口編程+讀取配置文件+工程設計模式等方式來取代new創建對象
- 定義接口
在業務層下定義接口,然后定義一個impl包,將UserServiceImpl類放到impl包下,這樣就可以將上面創建業務層對象改成:
UserService userService = new UserServiceImpl();
這樣的話就是實現左邊解耦了,如果擴展實現類只需要修改等號右邊
- 書寫properties文件
在resources下定義一個beans.properties文件,然后按照key=value格式書寫,key:userService,value:寫UserServiceImpl的全路徑名
- 使用反射+讀取配置文件創建對象
使用ResourceBundle抽象類去讀取properties配置文件,利用反射創建對象
- 定義一個工具類,使用工廠設計模式來創建對象
定義一個Map集合,key存properties文件內容=左邊的值,value存用反射創建的對象
代碼如下:
工廠類:
package com.itheima.case2.utils;import java.util.HashMap;
import java.util.ResourceBundle;/*TODO:當前工廠類的作用就是創建對象的回顧:一個類對象:1)單例 單個對象2)多例 多個對象我們這里實現產生的對象是單例 userService=com.itheima.case2.service.impl.UserServiceImpl1.創建一個Map集合: new HashMap<String,Object>();2.Map集合的key:例如配置文件等號左邊的標識userService roleService3.Map集合的value:就是創建的對象UserServiceImpl 類的對象 RoleServiceImpl類的對象4.實現步驟:1)創建map集合存儲創建的對象2)定義靜態方法創建具體類的對象3)在靜態方法中判斷map集合的key是否有值,如果沒有值,說明第一創建對象4)直接使用反射+讀取配置文件方式創建對象5)將創建的對象作為map集合的value和key存入到map中6)返回給調用者創建的對象*/
public class BeansFactory {// 1)創建map集合存儲創建的對象/*key valueuserService UserServiceImpl0x001roleService RoleServiceImpl0x002*/private static HashMap<String,Object> map = new HashMap<>();// 2)定義靜態方法創建具體類的對象//多線程安全問題:t1 t2public static synchronized <T> T getInstance(String key) throws Exception{//String key=userService roleService// 3)在靜態方法中判斷map集合的key是否有值,如果沒有值,說明第一創建對象Object obj = map.get(key);// 4)直接使用反射+讀取配置文件方式創建對象if(obj == null){//說明當前map集合中沒有傳遞的key對應的值//4.1使用反射+讀取配置文件創建對象ResourceBundle bundle = ResourceBundle.getBundle("beans");//參數beans表示要關聯的配置文件的名字,不能書寫后綴名//4.2根據配置文件的key獲取值 t1//userService=com.itheima.case2.service.impl.UserServiceImplString classNameStr = bundle.getString(key);//"com.itheima.case2.service.impl.UserServiceImpl"//4.3使用反射創建對象Class<?> clazz = Class.forName(classNameStr);obj = clazz.newInstance();//調用UserServiceImpl類的無參構造方法// 5)將創建的對象作為map集合的value和key存入到map中map.put(key,obj);//t1線程創建的對象0x001 t2線程創建的對象0x002}// 6)返回給調用者創建的對象return (T)obj;}}
配置文件:
userService=com.itheima.case2.service.impl.UserServiceImpl
web層UserServlet
UserService userService = BeanFactory.getInstance("userService");
IOC概述
Inversion of Control:控制反轉
以前我們要獲取對象,我們自己new主動獲取,現在有了工廠模式,我們需要獲取對象,是工廠創建,我們被動接收工廠創建的對象,這就是控制反轉,說白了就是ioc采用工廠模式創建對象達到解耦合
其實SpringIOC底層是Map集合,我們經常會說SpringIOC容器即Map集合。
SPI機制(為學習框架做思想和技術鋪墊)
SPI引入
1. 標準/規范
- 工程 spi_interface
- 只有一個接口car
2. 具體的實現
- 工程 honda_car 和 tesla_car
- 工程依賴了spi_interface
pom.xml- 有一個實現類,實現了標準
HondaCar implements Car
TeslaCar implements Car- 還有一個配置文件
1). 在類路徑classpath下
resources/META-INF/services
2). 文件名: 接口的全限定名
com.itheima.Car
3). 文件內容: 實現類的全限定名
com.itheima.impl.HondaCar3. 調用
- 工程 spi_test
- 工程依賴了 honda_car 和 tesla_car
- 測試類 SpiTest
測試類代碼
# ServiceLoader<Car> cars = ServiceLoader.load(Car.class);加載接口實現1. 加載當前工程依賴的所有工程 classpath:META-INF/services目錄下跟當前接口參數同名的文件(classpath:META-INF.services/com.itheima.Car文件)2. 當前案例依賴了兩個工程,那么這兩個工程的配置文件都會被讀取到honda_car===META-INF.services/com.itheima.Car文件中的com.itheima.impl.HondaCartesla_car===META-INF.services/com.itheima.Car文件中的com.itheima.impl.TeslaCar注意:配置文件名必須是實現的接口全路徑,配置文件中書寫實現類的全路徑3. 通過反射創建接口文件中配置的實例Class clazz= Class.forName("com.itheima.impl.TeslaCar");Car car = clazz.newInstance();
ServiceLoader類介紹
- ServiceLoader功能和ClassLoader功能類似,能裝載類文件,但是使用時是有區別的
- ServiceLoader裝載的是一系列有某種共同特征的實現類(類實現同一個接口,在實現類的工程中的resources目錄下存在META-INF/services目錄下接口同名的文件),即這些類實現接口或者抽象類。而ClassLoader是可以加載任何類的類加載器;
- ServiceLoader加載時需要特殊的配置:
- 在類路徑:classpath:META-INF/services/接口全路徑文件
- 在文件中配置實現類全路徑com.itheima.impl.HondaCar
- ServiceLoader還實現了Iterable接口,可以進行迭代
- 原理:在ServiceLoader.load的時候,根據傳入的接口Class對象,遍歷META-INF/services目錄下的以該接口命名的文件中的所有類,將創建實現類的對象返回。
SPI介紹
全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件
Java的SPI機制就是將一些類信息寫在約定的文件中,然后由特定的類加載器ServiceLoader加載解析文件獲取資源
Java SPI 基于"接口編程+策略模式+配置文件(約定)"組合實現的動態加載機制
運用場景:
場景 | 說明 |
---|---|
數據庫驅動 | 數據庫驅動加載接口實現類的加載 JDBC加載不同類型數據庫的驅動 |
日志門面SLF4J接口實現類加載 | SLF4J加載不同提供商的日志實現類 |
Spring | Spring中大量使用了SPI,比如:對servlet3.0規范對ServletContainerInitializer的實現、自動類型轉換Type Conversion SPI(Converter SPI、Formatter SPI)等 |
Dubbo | Dubbo中也大量使用SPI的方式實現框架的擴展, 不過它對Java提供的原生SPI做了封裝,允許用戶擴展實現Filter接口 |
SpringBoot | SpringBoot基于SPI思想實現自動裝配 |
SPI練習JDBC4.0免注冊驅動原理
public class JdbcDemo {public static void main(String[] args) throws Exception {//1.加載驅動
// Class.forName("com.mysql.jdbc.Driver");//2.獲取連接Connection connection = DriverManager.getConnection("jdbc:mysql:///dbvue", "root", "1234");System.out.println(connection);}
}
//DriverManager類源碼:
public class DriverManager {//靜態代碼塊static {loadInitialDrivers();println("JDBC DriverManager initialized");}private static void loadInitialDrivers() {.....AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {//spi/*1.java.sql.Driver 是一個接口2.java.sql.Driver接口實現類,com.mysql.jdbc.Driver使用SerciveLoader類加載器調用方法獲取java.sql.Driver接口的實現類對象放到LoadedDrivers中*/ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(java.sql.Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});......}
}
Servlet實現方式三 ServletContainerInitializer
前倆種方式是注解和xml
第三種就是spi方式,后面學習的框架底層就是采用這種方式
ServletContainerInitializer 是 Servlet 3.0 新增的一個接口,主要用于在容器啟動階段通過編程風格注冊Filter, Servlet以及Listener,以取代通過web.xml配置注冊。這樣就利于開發內聚的web應用框架.
運行原理
- ServletContainerInitializer接口的實現類通過java SPI聲明自己是ServletContainerInitializer 的提供者.
- web容器啟動階段依據java spi獲取到所有ServletContainerInitializer的實現類,然后執行其onStartup方法.
- 在onStartup中通過編碼方式將組件servlet加載到ServletContext
小結
ServletContainerInitializer 在web容器啟動時為提供給第三方組件機會做一些初始化的工作,例如注冊servlet或者filter等,servlet規范中通過ServletContainerInitializer實現此功能。每個框架要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄創建一個名為javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類,那么,當web容器啟動時就會運行這個初始化器做一些組件內的初始化工作