手写一个简易的类springMVC
2019-04-08 本文已影响0人
JaJIng
首先了解一下SpringBean的生命周期:
937513-20160507202024015-234747937.png
工程目录结构如图所示:
jjmvc.PNG
注解类JJAutowired:
package jj.mvc.anonation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.FIELD})
public @interface JJAutowired {
String value() default "";
}
注解类JJController:
package jj.mvc.anonation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE})
public @interface JJController {
String value() default "";
}
注解类JJRequestMapping:
package jj.mvc.anonation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface JJRequestMapping {
String value() default "";
}
注解类JJService:
package jj.mvc.anonation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE})
public @interface JJService {
String value() default "";
}
controller实现,我们的请求派发入口:
package jj.mvc.controller;
import jj.mvc.anonation.JJAutowired;
import jj.mvc.anonation.JJController;
import jj.mvc.anonation.JJRequestMapping;
import jj.mvc.anonation.JJService;
@JJController
@JJRequestMapping("jj/mv/controller")
public class BaseController {
@JJAutowired
private MyService myservice;
@JJRequestMapping("/test1")
public String test1(String name) {
return "hello "+name;
}
}
简单的service接口和实现:
package jj.mvc.service;
public interface MyService {
String doService(String name);
}
下面实现mvc核心的servlet相关:
首先是web.xml:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- org.springframework.web.servlet.DispatcherServlet -->
<servlet>
<servlet-name>jjmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>configMVC.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jjmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
配置文件configMVC.properties很简单,一个mvc包扫描字面量:
scanBase=jj.mvc
适配器类:
package jj.mvc.servlet;
import java.lang.reflect.Method;
public class HandlerAdapter {
Class<?> handlerClazz ;
Method handlerMethod;
String[] parameters;
public Class<?> getHandlerClazz() {
return handlerClazz;
}
public void setHandlerClazz(Class<?> handlerClazz) {
this.handlerClazz = handlerClazz;
}
public Method getHandlerMethod() {
return handlerMethod;
}
public void setHandlerMethod(Method handlerMethod) {
this.handlerMethod = handlerMethod;
}
public String[] getParameters() {
return parameters;
}
public void setParameters(String[] parameters) {
this.parameters = parameters;
}
}
最重要的代码,servlet,重点看doDispatch方法实现:
package jj.mvc.servlet;
import jj.mvc.anonation.JJAutowired;
import jj.mvc.anonation.JJController;
import jj.mvc.anonation.JJRequestMapping;
import jj.mvc.anonation.JJService;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;
public class JJservlet extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
private Properties properties=new Properties() ;
private List<Class<?>> clazzList=new ArrayList<Class<?>>();
private Map<String,Object> beanMap=new HashMap<String,Object>();
private Map<String,HandlerAdapter> requestMapping=new HashMap<String,HandlerAdapter>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
super.doPost(req, resp);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
try {
Object result=doDispatch(req,resp);
resp.getWriter().println(result);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
super.init(config);
//contextConfigLocation属性时我们在web.xml里注册过的。对应于本案例就是 "configMVC.properties"
String contextConfigLocation=config.getInitParameter("contextConfigLocation");
try {
properties.load(this.getClass().getResourceAsStream(contextConfigLocation));
//我们在configMVC.properties配置的scanBase属性值。
String scanBase=properties.getProperty("scanBase");
//其实就是springMVC的<context:component-scan base-package="xx.yyy.zzz" />
//扫描包,意味着scanBase值相关的这些路径下的java文件需要我们去扫描其注解,可以理解为@Component,只不过springMVC的实现颗粒度很细,我们可以指定@Controller,@Service,@Repositoty,include,exclude等
doScan(scanBase);
//初始化bean,也就是常说的控制反转IOC,当然我们这里没有实现一个专门的context上下文容器去很好的管理这些bean ,另外这里都是单例模式,而springMVC可以通过@scope实现非单例模式,比如原型模式等。
//在这里,我想说一个题外话,单例模式可以解决spring bean的@autowired属性注入循环依赖(a->b,b->c,c->a这种),原型则不行,
// 因为单例模式在初始化时有Map维护正在初始化的类的记录/原型则没有。当然如果通过构造器驻入,无论哪种模式都会报错。
initBean();
//这一步就是装配,也就是常说的依赖注入DI
doAutowired();
//注册url和处理器(这里只是简单的controller方法)的映射关系
doRequestMapping();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void doScan(String scanBase) {
File file =new File(scanBase);
//递归的扫描,是java字节码文件则把class类名加入clazzList,是文件夹则递归
if(file.isDirectory()) {
for(File lfile:file.listFiles()) {
doScan(lfile.getAbsolutePath());
}
}else {
clazzList.add(file.getClass());
}
}
public void initBean() throws InstantiationException, IllegalAccessException {
String beanName;
String annotationValue;
//从我们doScan注册的clazzList中遍历拿到需要初始化的bean类(@Component类)
for(Class<?> clazz:clazzList) {
if(clazz.isAnnotationPresent(JJController.class)) {
//如果是@JJController注解的bean,看看有没有写value,没有就取类名,比如我们的BaseController它的beanName就是BaseController
beanName=clazz.getSimpleName();
annotationValue=clazz.getAnnotation(JJController.class).value().trim();
if(!annotationValue.equals("")) {
beanName=annotationValue;
}
//初始化一个类实例放入beanMap(单例模式的实现,另外这里我们也没有实现构造器入参)
beanMap.put(beanName, clazz.newInstance());
}else if(clazz.isAnnotationPresent(JJService.class)) {
//如果是@JJService注解的bean,看看有没有写value,没有就取类名,比如我们的MyServiceImpl它的beanName就是MyServiceImpl
beanName=clazz.getSimpleName();
annotationValue=clazz.getAnnotation(JJService.class).value().trim();
if(!annotationValue.equals("")) {
beanName=annotationValue;
}
Object instance = clazz.newInstance();
beanMap.put(beanName, instance);
//因为在controller层,我们注入的service一般都抽象成接口,所以同样的实例我们要通过接口名做key再放一次
for(Class<?> interFace:clazz.getInterfaces()) {
beanMap.put(interFace.getSimpleName(), instance);
}
}
}
}
public void doAutowired() throws IllegalArgumentException, IllegalAccessException {
for(Entry<String, Object> beanEntry: beanMap.entrySet()) {
Object bean = beanEntry.getValue();
//如果一个bean的属性有autowired注解,那么需要从beanMap中拿出来把它装配进bean
for(Field beanFiled : bean.getClass().getFields()) {
if(beanFiled.isAnnotationPresent(JJAutowired.class)) {
/*System.out.println("beanFiled:"+beanFiled.getType().getName());
System.out.println("beanFiled Simple:"+beanFiled.getType().getSimpleName());*/
//请注意,这里有个问题,如果是被@autowired()注解的bean在initBean时,其beanName不是类名,那么无法装进来,除非实现对@Qualifier注解或者@Resource的提取,这里就不讨论了
String beanFiledType=beanFiled.getType().getSimpleName();
//反射装配
beanFiled.set(bean, beanMap.get(beanFiledType));
}
}
}
}
public void doRequestMapping() {
for(Class<?> clazz:clazzList) {
//映射表注册,因为@JJRequestMapping都在@JJController中,所以遍历@JJController修饰的类
if(clazz.isAnnotationPresent(JJController.class)) {
String urlbase="";
if(clazz.isAnnotationPresent(RequestMapping.class)) {
urlbase=clazz.getAnnotation(JJRequestMapping.class).value();
}
//实际上springMVC的实现不是这样的,估计会加上method.getModifiers(),应该是拿到当前类的public方法,也是为什么非public方法无法做controller映射的原因
for(Method method:clazz.getDeclaredMethods()) {
if(method.isAnnotationPresent(JJRequestMapping.class)) {
//urlMapping = 类的公共JJRequestMapping+方法JJRequestMapping
String urlMapping=urlbase+method.getAnnotation(JJRequestMapping.class).value();
//把处理类,处理方法封装成handlerAdapter,并绑定相应url添加入requestMapping
HandlerAdapter handlerAdapter=new HandlerAdapter();
handlerAdapter.setHandlerClazz(clazz);
handlerAdapter.setHandlerMethod(method);
requestMapping.put(urlMapping, handlerAdapter);
}
}
}
}
}
public Object doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String url=req.getRequestURI();
//拿到请求参数构建一个入参Map,这里我们只考虑普通的x-www-form-urlencoded提交,其他有关文件和其他流式的方式不做实现
Map<String, String[]> parametersMap= req.getParameterMap();
//构建入参数组
String[][] parameters=new String[parametersMap.size()][];
int i=0;
//填充入参数组
for(Entry<String, String[]> paraSet : parametersMap.entrySet()) {
parameters[i++]=paraSet.getValue();
}
//requestMapping是一个<url,请求处理器>的map,这是我们通过@JJController的@JJRequestMapping()里的值实现的,servlet在初始化时就已经把映射关系注入了这个map
HandlerAdapter handlerAdapter=requestMapping.get(url);
if(handlerAdapter!=null) {
/**
* 实际SpringMVC的处理很复杂,不是返回一个执行方法这么简单,大家可以去看看源码,大致通过一下几步:
* 1.将映射请求注册到处理器HandlerMapping;
* 2.HandlerMapping会把请求url映射为HandlerExecutionChain类型的handler对象;(包含过滤器的整个执行链)
* 3.将handler对象作为参数传递给HandlerAdapter的实例化对象,调用其handler方法会生成一个ModelAndView对象;并且在这个过程会有过滤器的环绕执行
* 我们这里只是简易的实现,并且也没有抽象成ModelAndView视图对象
*/
Method invokeMethod=handlerAdapter.getHandlerMethod();
//反射调用相关方法,注意:我们这里因为没有ModelAndView所以也就没有做视图处理,service 方法只是单纯的把方法return值通过流写回去。
return invokeMethod.invoke(handlerAdapter.getHandlerClazz(), handlerAdapter.getParameters());
}else {
//找不到则抛出错误;说明该url没有相关JJRequestMapping注册过
throw new IllegalAccessException();
}
}
}
HandlerExecutionChain.png