今天我們來實現一個簡單的springMVC框架,可以理解爲 springMVC1.0這個版本,只是功能比較簡單而已;
廢話不多說,進入正題;先看看springMVC的簡單流程;
我們請求的地址會被dispatchservlet這個springMVC的核心攔截器攔截,然後dispatchservlet會找到我們想要的那個controller裏的那個方法,並調用。但是dispatchServlet不是人,它沒那麼智能,看到url就知道是誰了,但是我們可以讓它變得智能起來,我們可以利用handlerMapping來告訴
dispatchServlet,它應該調用哪個方法;
爲了讓這個框架不那麼笨,我借用了spring的IOC 思想, 實現一個容器來管理我的bean; 這個框架和你印象中使用springmvc 很相似,你應該能回想起
springMVC的零零點點,然後看看這個簡單的框架是如何實現這些功能的;
首先看下項目工程:
首先,我們先看maven 依賴,沒錯,自己實現的框架當然不用spring的jar包了;
併爲了方便大家理解,我的取名和spring原來的風格多少有些類似;
首先看到annotation包,@Autowired、@Controller、@RequestMapping、@service這些註解大家應該再熟悉不過了吧!
然後是 servlet包,模仿的springMVC的核心攔截器 dispatchServlet;
demo包的 controller service 不必解釋啦~
這裏我們模仿springMVC的調用規則,MyDispatcherServlet負責處理框架的邏輯,調用Controller,Controller調用service;
先看看自定義註解是如何定義的,這裏挑選了幾個代表;
開始編寫核心攔截器 dispatchServilet
我們第一步模仿spring 的思想,先找到我們要掃描哪些類,下面是 spring的做法,
這是我的做法:
爲什麼我通過 String scanPackage = config.getInitParameter("scanPackage"); 就能找到xml中的配置呢?請參考這裏 的初始化細節;
servlet 對象在初始化的時候,容器會給它提供一個 ServletConfig 對象 去讀取 web.xml中的配置;
我們得到要掃描的路徑後,可以就需要實現spring的IOC了;
我們爲了得到所有bean;在拿到項目的包路徑後,可以轉換爲文件路徑,然後再從文件路徑中得到所有的類名;
得到類名後,就可以通過反射進行實例化了,然後將這些需要管理的東西放到一個容器中管理,要用的時候從容器裏拿就可以了;
我這裏使用的容器是 Hashmap<String, Object> 類的簡稱(SimpleName)爲key ,類的實例對象爲value。
得到了所有的類名後,開始實例化的工作
-
private void instance(){
-
-
if(classNames.size() == 0){ return; }
-
try{
-
for (String className : classNames) {
-
-
Class<?> clazz = Class.forName(className);
-
-
if(clazz.isAnnotationPresent(LANController.class)){
-
-
String beanName = lowerFirstChar(clazz.getSimpleName());
-
instanceMapping.put(beanName, clazz.newInstance());
-
}else if(clazz.isAnnotationPresent(LANService.class)){
-
LANService service = clazz.getAnnotation(LANService.class);
-
String beanName = service.value();
-
if(!"".equals(beanName.trim())){
-
-
instanceMapping.put(beanName, clazz.newInstance());
-
continue;
-
}
-
-
Class<?> [] interfaces = clazz.getInterfaces();
-
for (Class<?> i : interfaces) {
-
instanceMapping.put(i.getName(), clazz.newInstance());
-
}
-
}else{
-
continue;
-
}
-
}
-
}catch(Exception e){
-
e.getStackTrace();
-
}
-
}
實例化以後,就要準備注入了;
-
private void autowired(){
-
if(instanceMapping.isEmpty()){ return; }
-
for (Entry<String, Object> entry : instanceMapping.entrySet()) {
-
-
Field [] fields = entry.getValue().getClass().getDeclaredFields();
-
for (Field field : fields) {
-
if(!field.isAnnotationPresent(LANAutowired.class)){ continue; }
-
LANAutowired autowired = field.getAnnotation(LANAutowired.class);
-
-
field.setAccessible(true);
-
-
String beanName = autowired.value().trim();
-
System.out.println("beanName=="+beanName);
-
-
if("".equals(beanName)){
-
-
beanName = field.getType().getName();
-
}
-
try {
-
System.out.println("field.getName()***"+field.getName());
-
-
System.out.println("entry.getValue()======"+entry.getValue());
-
System.out.println("instanceMapping.get(beanName)---------"+instanceMapping.get(beanName));
-
-
field.set(entry.getValue(),instanceMapping.get(beanName));
-
} catch (Exception e) {
-
e.printStackTrace();
-
continue;
-
}
-
}
-
}
-
}
-
field.set(entry.getValue(),instanceMapping.get(beanName));
這一行代碼是關鍵,這就是爲什麼我們在注入接口,就能找到實現類的根本所在。
這裏的field,就是我們注入的那個接口, entry.getValue() 得到的是接口所在的類,instanceMapping.get(beanName)是這個接口對應的
那個實現類, 意思就是:在 運行階段, 將 controller中 的某個service接口字段 替換成 這個service的實現類;
這樣我們在編寫代碼的時候是用使用接口調用方法,但實際運行時,就是它的實現類在調用這個方法了;
不得不感嘆,反射的強大。
完成注入後,開始處理handlermapping上的value與之對應的method 的映射關係了
-
public void handlerMapping(){
-
if(instanceMapping.isEmpty()){ return; }
-
for (Entry<String, Object> entry : instanceMapping.entrySet()) {
-
Class<?> clazz = entry.getValue().getClass();
-
-
if(!clazz.isAnnotationPresent(LANController.class)){ continue; }
-
String url = "";
-
if(clazz.isAnnotationPresent(LANRequestMapping.class)){
-
LANRequestMapping requstMapping = clazz.getAnnotation(LANRequestMapping.class);
-
-
url = requstMapping.value();
-
}
-
Method [] methods = clazz.getMethods();
-
for (Method method : methods) {
-
if(!method.isAnnotationPresent(LANRequestMapping.class)){ continue; }
-
LANRequestMapping requstMapping = method.getAnnotation(LANRequestMapping.class);
-
String regex = url + requstMapping.value();
-
-
regex = regex.replaceAll("/+", "/").replaceAll("\\*", ".*");
-
System.out.println("regex: "+regex);
-
Map<String,Integer> pm = new HashMap<String,Integer>();
-
-
Annotation [] [] pa = method.getParameterAnnotations();
-
for(int i = 0; i < pa.length; i ++){
-
for (Annotation a : pa[i]){
-
if(a instanceof LANRequestParam){
-
String paramName = ((LANRequestParam) a).value();
-
if(!"".equals(paramName.trim())){
-
-
pm.put(paramName, i);
-
}
-
}
-
}
-
}
-
-
Class<?> [] paramsTypes = method.getParameterTypes();
-
for(int i = 0 ; i < paramsTypes.length; i ++){
-
Class<?> type = paramsTypes[i];
-
if(type == HttpServletRequest.class || type == HttpServletResponse.class){
-
-
pm.put(type.getName(), i);
-
}
-
}
-
handlerMapping.add(new Handler(Pattern.compile(regex),entry.getValue(),method, pm));
-
}
-
}
-
}
完成所有的初始化工作後,當然就等着用戶發過來的請求咯。
那自然是調用servlet的 doPost 和 doGet方法了,
爲了簡單點,我在doGet中調用doPost
-
@Override
-
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
this.doPost(req, resp);
-
}
-
-
@Override
-
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
-
try{
-
-
-
boolean isMatcher = pattern(req,resp);
-
if(!isMatcher){
-
resp.getWriter().write("對不起,你遇到了 404 Not Found");
-
}
-
}catch(Exception e){
-
resp.getWriter().write("500 Exception,Details:\r\n" +
-
e.getMessage() + "\r\n" +
-
Arrays.toString(e.getStackTrace()).replaceAll("\\[\\]", "")
-
.replaceAll(",\\s", "\r\n"));
-
}
-
}
如果如果沒有匹配成功就返回404,說明用戶的路徑輸錯了,
發送異常就報500;
如果匹配成功怎麼辦? 當然是調用controller裏的方法咯;
怎麼調用?還是通過反射~通過方法的反射~
-
public boolean pattern(HttpServletRequest req, HttpServletResponse resp) throws Exception{
-
if(handlerMapping.isEmpty()){ return false; }
-
-
String url = req.getRequestURI();
-
-
String contextPath = req.getContextPath();
-
url = url.replace(contextPath, "").replaceAll("/+", "/");
-
for (Handler handler : handlerMapping) {
-
try{
-
Matcher matcher = handler.pattern.matcher(url);
-
-
if(!matcher.matches()){ continue ;}
-
-
Class<?> [] paramTypes = handler.method.getParameterTypes();
-
-
Object [] paramValues = new Object[paramTypes.length];
-
-
Map<String,String[]> params = req.getParameterMap();
-
for (Entry<String, String []> param : params.entrySet()) {
-
String value = Arrays.toString(param.getValue()).replaceAll("\\]|\\[", "").replaceAll(",\\s", ",");
-
if(!handler.paramMapping.containsKey(param.getKey())){ continue;}
-
-
int index = handler.paramMapping.get(param.getKey());
-
-
paramValues[index] = castStringValue(value,paramTypes[index]);
-
}
-
-
int reqIndex = handler.paramMapping.get(HttpServletRequest.class.getName());
-
paramValues[reqIndex] = req;
-
int respIndex = handler.paramMapping.get(HttpServletResponse.class.getName());
-
paramValues[respIndex] = resp;
-
-
handler.method.invoke(handler.controller, paramValues);
-
return true;
-
}catch(Exception e){
-
throw e;
-
}
-
}
-
return false;
-
}
我們來看看controller的代碼,準備測試:
-
public class MyController {
-
-
public MyController() {
-
System.out.println("初始化了"+this);
-
}
-
-
@LANAutowired
-
private searchService searchService;
-
-
@LANAutowired("aa")
-
private modifyService modifyService;
-
-
@LANRequestMapping("/search/*.json")
-
public void search(HttpServletRequest request,HttpServletResponse response,
-
@LANRequestParam("name") String name){
-
String result = searchService.search(name);
-
System.out.println("查詢成功");
-
out(response,result);
-
}
-
-
-
@LANRequestMapping("/add.json")
-
public void add(HttpServletRequest request,HttpServletResponse response,
-
@LANRequestParam("name") String name,
-
@LANRequestParam("addr") String addr){
-
System.out.println("添加成功");
-
String result = modifyService.add(name,addr);
-
out(response,result);
-
}
-
-
-
@LANRequestMapping("/delete.json")
-
public void remove(HttpServletRequest request,HttpServletResponse response,
-
@LANRequestParam("id") Integer id){
-
System.out.println("刪除成功");
-
String result = modifyService.remove(id);
-
out(response,result);
-
}
是不是和你剛學springMVC時的一致?
趕緊啓動項目在地址欄輸入:http://localhost:8080/LanSpringMVC/web/add.json?name=witt&addr=shenzhen
出現:
-
add name = witt,addr=shenzhen
說明大工告成~
想要源碼的小夥伴點這裏:源碼