我們都知道spring的執行原理,甚至爲了面試倒背如流(http請求–>dispatcherServlet–>HandlerMapping–>Handler–>…),我之前雖然是知道這些東西,但是感覺對它又很模糊,dispatcherServlet具體是什麼?HandlerMapping又是幹什麼的?IOC、DI是怎麼實現的?這些東西在腦海中都是似懂非懂的。百度查資料什麼的其實都不如自己手寫一遍來的實在。也就幾百行代碼,手寫一遍,相信你一定會變得更自信。
注意:代碼中註釋都很全。源碼地址:
https://gitee.com/isczy/mySpringCore.git
1.準備工作
配置application.properties,就和xml中配置的一個性質
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>WebApplication</display-name>
<servlet>
<servlet-name>MySpringMvc</servlet-name>
<servlet-class>com.czy.project.springMVCframework.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MySpringMvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
2.編寫自定義註解
自定義一些常用的註解:如
@Autowired
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
@Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
@RequestMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
@RequestParam
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value() default "";
}
@Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
3.編寫簡單的Controller、Service
@MyController
@MyRequestMapping("/demo")
public class DemoController {
@MyAutowired
private DemoService demoService;
@MyRequestMapping("/query")
public void query(HttpServletRequest req, HttpServletResponse resp,
@MyRequestParam("name") String name,@MyRequestParam("age") int age){
String result = demoService.query(name)+" and age is "+age;
try {
resp.setHeader("Content-type", "text/html;charset=UTF-8");
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@MyRequestMapping("/add")
public void add(HttpServletRequest req, HttpServletResponse resp,
@MyRequestParam("name") String name){
String result = demoService.add(name);
try {
resp.setHeader("Content-type", "text/html;charset=UTF-8");
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@MyRequestMapping("/remove")
public void remove(HttpServletRequest req,HttpServletResponse resp,
@MyRequestParam("name") String name){
try {
String result = demoService.remove(name);
resp.setHeader("Content-type", "text/html;charset=UTF-8");
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
public interface DemoService {
String query (String name);
String add(String name);
String remove(String name);
}
@MyService
public class DemoServiceImpl implements DemoService {
@Override
public String query(String name) {
return "query success :My name is " + name ;
}
@Override
public String add(String name) {
return "添加成功:新增用戶 " + name ;
}
@Override
public String remove(String name) {
return "刪除成功:刪除用戶 " + name ;
}
}
4.編寫核心重點:DispatcherServlet
/**
* 入口類
* 即spring中的DispatcherServlet
* @author czy
*/
public class MyDispatcherServlet extends HttpServlet {
private static final String LOCATION = "contextConfigLocation";//即web.xml中配置的init-param-name
private Properties properties = new Properties();//用於保存配置文件application.properties的內容
private List<String> classNames = new ArrayList<String>();//保存所有掃描出的全限定類名的集合
private Map<String,Object> ioc = new HashMap<String,Object>();//ioc容器
//保存所有的Url和方法的映射關係
private List<Handler> handlerMapping = new ArrayList<Handler>();
/**
* 初始化一系列操作
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//1.加載配置文件
//根據contextConfigLocation名稱從web.xml中獲取出配置文件全稱:application.properties
String initParameter = config.getInitParameter(LOCATION);
doLoadConfig(initParameter);
/**
* 2.掃描指定包下的類:spring中在application.xml中配置scanPackage指定掃描的包路徑
* 這裏直接用application.properties代替
*/
String scanPackage = properties.getProperty("scanPackage");//通過scanPackage鍵獲取值,即獲取要掃描的包路徑
doScanner(scanPackage);
//3.初始化所有相關類的實例(這裏就是初始化加@MyController,@MyService的類),並保存到IOC容器中
doInstance();
//4.依賴注入
doAutowired();
//5.構造HandlerMapping
initHandlerMapping();
//******************到此爲止,spring相關ioc、di等初始化完成**********************
//******************等待請求,匹配URL,定位方法, 反射調用執行*******************
//******************調用doGet或者doPost方法*************************************
System.out.println("MyDispatcherServlet[初始化完成]。。。等待調用。。。");
}
/**
* doGet和doPost方法大家應該相當瞭解了
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);//doGet的操作交個doPost來執行
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try{
doDispatch(req,resp); //開始匹配到對應的方法
}catch (Exception e){
//如果匹配過程出現異常,將異常信息打印出去
resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace()).
replaceAll("\\[|\\]", "").
replaceAll(",\\s", "\r\n"));//將‘[’或者‘]’替換成"",將空白字符替換成換行符
}
}
/**
*匹配URL
* @param req
* @param resp
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
Handler handler = getHandler(req);
if (null == handler){
//如果沒有匹配上,返回404錯誤
resp.getWriter().write("404 Not Found");//常見操作。。。
return;
}
System.out.println("已匹配到處理器["+handler+"]");
//獲取方法的參數列表
Class<?>[] paramTypes = handler.method.getParameterTypes();
//保存所有需要自動賦值的參數值
Object [] paramValues = new Object[paramTypes.length];
Map<String,String[]> parameterMap = req.getParameterMap();//獲取用戶傳入的參數map
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
//獲取所有參數即數組,並將數組轉化成string,並替換[]爲""、空白字符爲","
String value = new String(Arrays.toString(param.getValue()).getBytes("iso8859-1"),"utf-8").
replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
//如果handler的參數列表沒有用戶傳入的key則跳出本次循環
if (!handler.paramIndexMapping.containsKey(param.getKey()))continue;
//找到匹配的參數,則開始填充參數值
int index = handler.paramIndexMapping.get(param.getKey());//根據參數名獲取參數對應的序號
//url傳過來的參數都是String類型的,HTTP是基於字符串協議
//只需要把String轉換爲所需類型就好
paramValues[index] = convert(paramTypes[index],value);
}
//設置方法中的request和response對象
int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
paramValues[reqIndex] = req;
int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
paramValues[respIndex] = resp;
//執行url對應的方法
System.out.println("開始執行url對應的方法:"+handler.method.getName());
handler.method.invoke(handler.controller, paramValues);
}
/**
*獲取處理器Handler
* @param request
* @return
*/
private Handler getHandler(HttpServletRequest request) {
if (handlerMapping.isEmpty())return null;
String url = request.getRequestURI();
String contextPath = request.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/");
for (Handler handler : handlerMapping) {
Matcher matcher = handler.pattern.matcher(url);//根據用戶的url獲取一個匹配器matcher
//matches方法用於全字符串匹配也就是100%匹配
if (!matcher.matches())continue;//如果沒有匹配到則跳過本次循環
return handler;
}
return null;
}
/**
* 初始化HandlerMapping(處理器映射器):
* HandlerMapping其實就是一個包含Handler的集合
*/
private void initHandlerMapping() {
if (ioc.isEmpty())return;
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
//如果該類上沒有MyController註解則跳過本次循環
if (!clazz.isAnnotationPresent(MyController.class))continue;
String url = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)){
//如果類上有MyRequestMapping註解,獲取Controller的url配置
url =clazz.getAnnotation(MyRequestMapping.class).value();
}
//獲取Method的url配置
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//沒有加RequestMapping註解的直接忽略
if(!method.isAnnotationPresent(MyRequestMapping.class))continue;
//映射URL
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
String regex =("/"+url+"/"+requestMapping.value()).replaceAll("/+","/");
Pattern pattern = Pattern.compile(regex);
handlerMapping.add(new Handler(entry.getValue(),method,pattern));
System.out.println("HandlerMapping初始化完成【url:" + regex +"】" +"【method:" + method+"】");
}
}
}
/**
* 依賴注入:就是拿到ioc容器中的類,然後訪問類中的字段屬性,是否包含Autowired註解
* 然後從ioc容器中拿到實例並初始化該字段
*/
private void doAutowired() {
if (ioc.isEmpty())return;
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//拿到實例對象中的所有屬性
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
//如果該屬性上沒有MyAutowired註解則跳過本次循環
if (!field.isAnnotationPresent(MyAutowired.class))continue;
MyAutowired autowired = field.getAnnotation(MyAutowired.class);
String beanName = autowired.value();
if ("".equals(beanName)){//用戶沒有指定注入的beanName
beanName = field.getType().getName();//那麼beanName就是該屬性的名稱
}
field.setAccessible(true);//暴力訪問私有屬性
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
continue ;
}
}
}
}
/**
* 初始化所有相關類的實例,並保存到IOC容器中
*/
private void doInstance() {
if(classNames.size() == 0)return;
//遍歷全限定類名集合,拿到各類,並初始化
try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)){ //如果類上聲明瞭MyController註解
//將類名作爲beanName,即ioc容器的key
String beanName = clazz.getName();
ioc.put(clazz.getName(),clazz.newInstance());
System.out.println("["+beanName+"]已添加到IOC容器");
}else if (clazz.isAnnotationPresent(MyService.class)){//如果類上聲明瞭MyService註解
MyService myService = clazz.getAnnotation(MyService.class);
String beanName = myService.value();//獲取自定義的beanName
if (!"".equals(beanName.trim())){//如果用戶設置了自定義的beanName,就用用戶自己設置
ioc.put(beanName,clazz.newInstance());
System.out.println("["+beanName+"]已添加到IOC容器");
continue;
}
//如果自己沒設,就按接口類型創建一個實例
Class<?>[] interfaces = clazz.getInterfaces();//獲取該類實現的接口類
for (Class<?> c : interfaces) {
ioc.put(c.getName(),clazz.newInstance());
System.out.println("["+c.getName()+"]已添加到IOC容器");
}
}else{
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 掃描指定包下的類
* @param scanPackage:包掃描路徑
*/
private void doScanner(String scanPackage) {
//將包路徑轉換爲文件路徑:即將com.czy.project.demo轉化爲 com/czy/project/demo
String s = scanPackage.replace(".", "/");
URL url = this.getClass().getClassLoader().getResource(s);
File dir = new File(url.getFile());//獲取該路徑的文件
//遍歷該文件夾下的所有文件
for (File file : dir.listFiles()) {
if (file.isDirectory()){
doScanner(scanPackage+"."+file.getName());//如果該文件是個文件夾,繼續遞歸
}else {
//將全限定類名添加到集合
classNames.add(scanPackage+"."+file.getName().replace(".class","").trim());
}
}
for (String className : classNames) {
System.out.println("已掃描到:["+className+"]");
}
}
/**
* 加載配置文件:application.properties
* @param initParameter
*/
private void doLoadConfig(String initParameter){
//將application.properties加載到輸入流
try(InputStream is = this.getClass().getClassLoader().getResourceAsStream(initParameter)) {
properties.load(is);//讀取配置文件
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 內部類
* Handler記錄Controller中的RequestMapping和Method的對應關係
* 即spring中一個url請求對應一個方法
*/
private class Handler{
protected Object controller; //保存方法對應的實例
protected Method method; //保存映射的方法
protected Pattern pattern; //url對應的正則表達式,用於映射匹配
protected Map<String,Integer> paramIndexMapping; //參數名以及序號
/**
*構造一個Handler基本的參數
* @param controller
* @param method
* @param pattern
*/
public Handler(Object controller, Method method, Pattern pattern) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
paramIndexMapping = new HashMap<String, Integer>();
putParamIndexMapping(method);
}
/**
* 處理方法中的參數
* @param method
*/
private void putParamIndexMapping(Method method) {
//獲取方法參數上的註解,注意Annotation是一個二維數組
Annotation[][] p = method.getParameterAnnotations();
for (int i = 0; i < p.length; i++) {
for (Annotation a : p[i]) {
if (a instanceof MyRequestParam){//如果這個註解是MyRequestParam
String param = ((MyRequestParam) a).value();
if (!"".equals(param)){
paramIndexMapping.put(param,i);
}
}
}
}
//提取方法中的request和response參數
Class<?>[] parameterTypes = method.getParameterTypes();//獲取所有參數的類型
for (int i = 0; i < parameterTypes.length ; i ++) {
Class<?> type = parameterTypes[i];
if (type == HttpServletRequest.class ||
type == HttpServletResponse.class){
paramIndexMapping.put(type.getName(),i);
}
}
}
@Override
public String toString() {
return "Handler{" +
"controller=" + controller +
", method=" + method +
", pattern=" + pattern +
", paramIndexMapping=" + paramIndexMapping +
'}';
}
}
/**
* HTTP是基於字符串協議,所以url傳過來的參數都是String類型的,
* 只需要把String轉換爲對應方法中的類型就好
* @param type
* @param value
* @return
*/
private Object convert(Class<?> type,String value){
if(Integer.class == type||type == int.class){
return Integer.valueOf(value);
}
//如果還有double或者其他類型,繼續加if
//這時候,我們應該想到策略模式了
return value;
}
}
5.測試
啓動項目後可以看到,初始化MyDispatcherServlet都做了什麼
瀏覽器地址輸入路徑:http://localhost:8088/demo/query?name=zhangsan&age=18
訪問結果:query success :My name is zhangsan and age is 18