网上很多关于springmvc的分析,但很少有手动实现的,这里通过一个简单的demo实现mvc基本功能,仅供学习参考。
我们先简单了解下springmvc请求流程,如下图:
从图上得知,最先处理请求的是dispatcherServlet,它接受请求并查询得到拦截器链HandlerExecutionChain,HandlerAdapter经过适配器调用具体的处理器(Controller).
查看源码可以到spring-webmvc.jar中,org.springframework.web.servlet/DispatcherServlet.class,其中方法doDispatch()完成了一个请求到返回数据的完整操作.
下面我们开始动手了,先创建一个javaweb工程,写一个Servlet,如下:
@WebServlet(urlPatterns = "/*", loadOnStartup = 1)
public class DispatcherServlet extends HttpServlet {
private List<String> clzList = new ArrayList<>();
private Map<String, Object> beansMap = new HashMap<>();
private Map<String, Object> urlMapping = new HashMap<>();
@Override
public void init() throws ServletException {
try {
//扫描配置 这里是包名
scanPackages("com.iti.smvc");
//实例化对象
doInstance();
//建立对象之间的依赖ioc
ioc();
//建立url到controller的映射
doMapping();
} catch (Exception e) {
e.printStackTrace();
}
}
}
注:servlet3.0不再需要web.xml, 这里将成员变量写入servlet并不是很好的实现,会导致线程不安全
这个servlet要做一些初始化的工作,如:
1.扫描包名,将class对象装入clzList列表
2.遍历clzList列表,实例化有Controller和Service标注的类
3.依赖注入,将service注入到controller
4.建立url与controller中方法url的mapping关系
我们依次来实现他们:
下面的是扫描配置方法
private void scanPackages(String packageUrl) {
String fileUrl = getClass().getClassLoader().getResource("/"+packageUrl.replaceAll("\\.", "/")).getFile();
File scanfile = new File(fileUrl);
String[] fileList = scanfile.list();
for (String fileName: fileList) {
File file = new File(fileUrl+fileName);
if (file.isDirectory()) {
scanPackages(packageUrl + "." + fileName);;
} else {
clzList.add(packageUrl + "." + fileName.replace(".class", ""));
}
}
}
接着是对象实例化:
private void doInstance() throws Exception{
if (clzList.size()>0) {
for (String clzName : clzList) {
Class<?> clzClass = Class.forName(clzName);
if (clzClass.isAnnotationPresent(Controller.class)) {
//for controller
RequestMapping rm = clzClass.getAnnotation(RequestMapping.class);
beansMap.put(rm.value(), clzClass.newInstance());
} else if (clzClass.isAnnotationPresent(Service.class)) {
Service service = clzClass.getAnnotation(Service.class);
beansMap.put(service.value(), clzClass.newInstance());
}
}
}
}
接着是依赖注入,其实是反射:
private void ioc() throws Exception{
if (beansMap.size()>0) {
for (Map.Entry<String, Object> entry : beansMap.entrySet()) {
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
Qualifier anno = field.getAnnotation(Qualifier.class);
field.set(entry.getValue(), beansMap.get(anno.value()));
}
}
}
}
}
最后是建立请求url与controller的mapping关系,如下:
private void doMapping() {
if (beansMap.size()>0) {
for (Map.Entry<String, Object> entry: beansMap.entrySet()) {
Class<? extends Object> obj = entry.getValue().getClass();
if (obj.isAnnotationPresent(Controller.class)){
RequestMapping rm = obj.getAnnotation(RequestMapping.class);
Method[] methods = obj.getMethods();
for (Method method: methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping anno = method.getAnnotation(RequestMapping.class);
urlMapping.put(rm.value() + anno.value(), method);
}
}
}
}
}
}
还要把注解创建下:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Qualifier {
String value() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
此时就可以启动服务完成初始化工作了
下面我们要创建一个sevice方法来接收url请求
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestURI = req.getRequestURI();
String contextPath = req.getContextPath();
//得到请求地址
String path = requestURI.replace(contextPath, "");
Method method = (Method)urlMapping.get(path);
if (method != null) {
try {
Object obj = method.invoke(beansMap.get("/"+path.split("/")[1]));
resp.getOutputStream().write(obj.toString().getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面是得到请求url,然后从urlMapping得到要调用的method,再通过反射调用,最后通过outputStream输出到浏览器上。
下面编写一个controller和service测试下
controller代码如下:
@Controller
@RequestMapping("/hello")
public class HelloController {
@Autowired
@Qualifier("helloservice")
Helloservice helloservice;
@RequestMapping("/sayHello")
public String sayHello() {
//System.out.println(helloservice.hello());
return "controller";
}
}
service代码如下:
@Service("helloservice")
public class Helloservice {
public String hello() {
return "hello";
}
}
请求url:http://localhost:8080/hello/sayHello
学习交流,欢迎加群:64691032
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。