本文纯手写springmvc核心功能,主要通过反射,注解的形式帮助大家清晰了解spring的核心思想。
1.idea新建web maven工程
2.配置web.xml,注入DispatcherServlet
Archetype Created Web Application
gfhMvc
com.gfh.mvc.framework.DispatcherServlet
contextConfigLocation
application.properties
1
gfhMvc
/*
新增application.properties,写入扫描的包 scanPackage=com.gfh.mvc.framework
3.初始化配置文件
private void initConfig(ServletConfig config) {
System.out.println("initConfig");
InputStream is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation"));
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != is)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.扫描包下的类,并注入ioc容器
/**
* 扫描包路径下的controller,并完成ioc注入
*
* @param contextPath:扫描包的路径
*/
private void scanPackage(String contextPath) {
URL url = this.getClass().getClassLoader().getResource("/" + contextPath.replaceAll("\\.", "/"));
File classDir = new File(Objects.requireNonNull(url).getFile());
for (File file : Objects.requireNonNull(classDir.listFiles())) {
if (file.isDirectory()) {
scanPackage(contextPath + "." + file.getName());
continue;
}
//不是文件夹,是class类
classList.add((contextPath + "." + file.getName()).replaceAll(".class", ""));
}
//反射实例化,存放入ioc容器中
for (String clsName : classList) {
try {
Class> cls = Class.forName(clsName);
//查询是否包含指定注解,如果不包含,则不注入
if (!isAnnotationPresent(cls, classes))
continue;
if (cls.isAnnotationPresent(Controller.class)) {
iocMap.put(lowerFirstCapse(cls.getSimpleName()), cls.newInstance());
} else if (cls.isAnnotationPresent(Service.class)) {
iocMap.put(lowerFirstCapse(cls.getSimpleName()), cls.newInstance());
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
5.自动注入service,使用注解@Autowired
/**
* 自动注入service
*/
private void autowired() {
for (Map.Entry entry : iocMap.entrySet()) {
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Autowired.class)) continue;
try {
field.setAccessible(true);
field.set(entry.getValue(), iocMap.get(lowerFirstCapse(field.getType().getSimpleName())));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
扫描ioc实例化过后的controller,找出带有注解@Autowired的field,并且将改field进行反射注入
6.注册路径映射,拦截路径并解析到容器中
private void initHandlerMapping() {
for (Map.Entry entry : iocMap.entrySet()) {
Class> cls = entry.getValue().getClass();
if (!cls.isAnnotationPresent(Controller.class))
continue;
String baseUrl = cls.getAnnotation(RequestMapping.class).value();
Method[] methods = cls.getMethods();
if (null == methods)
continue;
if (methods.length == 0) continue;
for (Method method : methods) {
if (!method.isAnnotationPresent(RequestMapping.class)) continue;
urlMap.put(baseUrl + method.getAnnotation(RequestMapping.class).value(), method);
}
}
}
7.doDispatch方法处理来访
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500");
}
}
如果该方法出现异常,则返回500的错误给到前端.如果路径容器中不包含来访路径,则返回404,下面是处理来访的核心代码
/**
* 处理get,post反射
*
* @param req :HttpServletRequest
* @param resp:HttpServletResponse
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String uri = req.getRequestURI();
if (!urlMap.containsKey(uri)) {
resp.getWriter().write("404");
return;
}
resp.setContentType("text/html;charset=UTF-8");
req.setCharacterEncoding("UTF-8");
Map parameterMap = req.getParameterMap();
Method method = urlMap.get(uri);
String clsName = lowerFirstCapse(method.getDeclaringClass().getSimpleName());
Object o = iocMap.get(clsName);
method.setAccessible(true);
Parameter[] parameters = method.getParameters();
Object[] paramValues = new Object[parameters.length];
int i = 0;
for (Map.Entry entry : parameterMap.entrySet()) {
if (parameters[i].getName().equals(entry.getKey())) {
Object val = entry.getValue()[0];
//此处只做几个简单数据类型展示,后续可以在此基础上拓展
switch (parameters[i].getType().toString()) {
case "class java.lang.Integer":
paramValues[i] = Integer.parseInt(val.toString());
break;
case "class java.lang.String":
paramValues[i] = val;
break;
default:
paramValues[i] = JSON.parseObject(val.toString(), parameters[i].getType());
break;
}
}
i++;
}
try {
Object result = method.invoke(o, paramValues);
resp.getWriter().write(JSON.toJSONString(result));
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
8.测试controller
package com.gfh.mvc.framework.controller; import com.gfh.mvc.framework.User; import com.gfh.mvc.framework.annotation.Autowired; import com.gfh.mvc.framework.annotation.Controller; import com.gfh.mvc.framework.annotation.RequestMapping; import com.gfh.mvc.framework.service.iml.TestServiceIml; @Controller @RequestMapping("/test") public class TestController { @Autowired private TestServiceIml testServiceIml; @RequestMapping("/test") public User user(String name, Integer age) { System.out.println("请求来自test:=" + name + ";age=" + age + ";"); testServiceIml.say("你在说什么呢"); User user = new User(); user.setName("gfh"); return user; } }
启动项目后,在浏览器输入http://localhost:8080/test/test?name=df
控制台打印service方法结果
到此便实现了spring的核心部分,需要注意的是,笔者使用的是jdk1.8环境,获取类的Parameter需要配置idea,如下图
代码已上传至github开源仓库