手寫SpringMVC (一) 簡要版,去除冗餘複雜程式碼,手寫Spring核心功能
阿新 • • 發佈:2019-02-18
github 地址 :https://github.com/yjy91913/jerry-mvcframework
只是閒來無事寫的簡化版,僅供大家理解SpringMvc的運作原理)
瞭解了springMVC的原始碼,寫一個功能簡單可以實現的springMVC,只是為了深入瞭解spring
先看一下專案結構
demo是測試類
第一步:pom檔案
加入依賴 -servlet
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId >
<version>3.0.1</version>
</dependency>
第二步:配置web.xml
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="true" version="3.0">
<display-name>
Jerry web application
</display-name>
<!--配置一個servlet,這個配置一個servlet就是springMVC中的DispatcherServlet-->
<servlet>
<servlet-name>jmvc</servlet-name>
<servlet-class >com.jerry.mvcframework.servlet.JDispatcherServlet</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>jmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
第三步:配置掃描包的位置
這一步簡化了,只有讀取掃描包的位置
resources 目錄下建立一個配置檔案application.properties
scanPackage=com.jerry.demo
第四步:配置所有的常用註解
這個只寫一下常用的註解
@JAutowired
@Target({ElementType.FIELD}) //欄位、列舉的常量
@Retention(RetentionPolicy.RUNTIME) //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented //說明該註解將被包含在javadoc中
public @interface JAutowired {
String value() default "";
}
@JController
@Target({ElementType.TYPE}) //介面、類、列舉、註解
@Retention(RetentionPolicy.RUNTIME) //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented //說明該註解將被包含在javadoc中
public @interface JController {
String value() default "";
}
@JRequestMapping
@Target({ElementType.METHOD,ElementType.TYPE}) //欄位、列舉的常量 方法
@Retention(RetentionPolicy.RUNTIME) //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented //說明該註解將被包含在javadoc中
public @interface JRequestMapping {
String value() default "";
}
@JRequestParam
@Target({ElementType.PARAMETER}) //方法引數
@Retention(RetentionPolicy.RUNTIME) //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented //說明該註解將被包含在javadoc中
public @interface JRequestParam {
String value() default "";
}
@JService
@Target({ElementType.TYPE}) //介面、類、列舉、註解
@Retention(RetentionPolicy.RUNTIME) //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented //說明該註解將被包含在javadoc中
public @interface JService {
String value() default "";
}
第五步:建立JDispatcherServlet
我們所要的做的事情,就是實現這個類
原聲Servlet主要方法我們要處理就兩個 一個:init doPost doGet 後面兩個其實是一個方法
第六步:編寫init()方法
主要需要實現的功能都在註釋裡
@Override
public void init(ServletConfig config) throws ServletException {
//1.載入配置檔案
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2.掃描所有的相關的類
doScanner(properties.getProperty("scanPackage"));
//3.初始化所有相關的類Class的例項,並且將其儲存到IOC容器
doInstance();
//4.自動化的依賴注入
doAutowired();
//5.初始化HandlerMapping
initHandlerMapping();
System.out.println("Jerry MVC Framework is init");
}
第六步:編寫doLoadConfig()方法
private void doLoadConfig(String configLocation){
//獲取一個inputStream
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
//載入配置檔案
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
第六步:編寫doScanner()方法
這部是掃描我們指定的目錄
//初始化一個List,用於存放.class的地址
private List<String> classNames = new ArrayList<String>();
private void doScanner(String packageName){
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
//獲取所有的位元組碼檔案
File classDir = new File(url.getFile());
for (File file : classDir.listFiles()) {
//如果是資料夾遞迴執行
if(file.isDirectory()){
doScanner(packageName + "." + file.getName());
} else {
//輸入是.class,加入list
classNames.add(packageName + "." + file.getName().replace(".class",""));
}
}
}
第七步:編寫doInstance()方法
初始化所有相關的類Class的例項,並且將其儲存到IOC容器
//初始化ioc容器
private Map<String,Object> ioc = new HashMap<String, Object>();
//寫一個講第一個字母變成小寫的方法
private String lowerFirst(String str) {
char[] chars = str.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
//編寫doInstance()
private void doInstance(){
if(classNames.isEmpty()){
return;
}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
//進行例項化
//判斷,是否有Controller等註解
if(clazz.isAnnotationPresent(JController.class)){
String beanName = lowerFirst(clazz.getSimpleName());
ioc.put(beanName,clazz.newInstance());
}else if (clazz.isAnnotationPresent(JService.class)){
JService jService = clazz.getAnnotation(JService.class);
String beanName = jService.value();
//1.預設採用類名首字母小寫 beanId
//2.如果自定義名字,優先使用自己定義的名字
if("".equals(beanName.trim())){
beanName = lowerFirst(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanName,instance);
//3.根據型別匹配,利用實現類的介面名字作為key,實現類的類做為value
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
ioc.put(lowerFirst(i.getSimpleName()),instance);
}
}else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
第七步:編寫doAutowired()方法
自動化的依賴注入,掃描所有帶autowired註解
private void doAutowired(){
if(ioc.isEmpty()){
return;
}
for (Map.Entry<String,Object> entry : ioc.entrySet()) {
//在Spring中,沒有隱私
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
//找autowired
if(!field.isAnnotationPresent(JAutowired.class)){
continue;
}
JAutowired jAutowired = field.getAnnotation(JAutowired.class);
String beanName = jAutowired.value().trim();
if("".equals(beanName)){
beanName = lowerFirst(field.getType().getSimpleName());
}
//暴力反射
field.setAccessible(true);
try {
field.set(entry.getValue(),ioc.get(beanName));
System.out.println(entry.getValue()+" is autowired ,object is "+ioc.get(beanName));
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
第七步:初始化HandlerMapping
初始化一個HandlerMapping
private Map<String,Handler> handlerMapping = new HashMap<String, Handler>();
寫一個handler類
@Data
@ToString
private class Handler {
protected Object controller;
protected Method method;
protected Pattern pattern;
protected Map<String,Integer> paramIndexMapping;
}
編寫initHandlerMapping()方法
private void initHandlerMapping() {
if(ioc.isEmpty()){
return;
}
for (Map.Entry<String,Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
//HandlerMapping 只認JController
if(!clazz.isAnnotationPresent(JController.class)){
continue;
}
String url = "";
if(clazz.isAnnotationPresent(JRequestMapping.class)){
JRequestMapping jRequestMapping = clazz.getAnnotation(JRequestMapping.class);
url = jRequestMapping.value().trim();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//如果沒有JRequestMapping ,直接跳過
if(!method.isAnnotationPresent(JRequestMapping.class)){
continue;
}
JRequestMapping jRequestMapping = method.getAnnotation(JRequestMapping.class);
String murl = url + jRequestMapping.value().trim();
Handler handler = new Handler();
handler.setController(entry.getValue());
handler.setMethod(method);
handlerMapping.put(murl,handler);
System.out.println("Mapping : "+ murl + " " +handler);
}
}
}
到這裡springMVC初始化方法已經寫完了,下面就是
doDispatch()方法了 ,下次在寫