手写简易版SpringMVC
为了更好了Spring以及SpringMVC的核心原理,本文参考其他书籍实现了一个简易版的SpringMVC框架,框架的构建过程如下:
创建个Maven工程,并在pom.xml文件中引入Servlet;
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
- 工程结构如下:
在web.xml里配置前端控制器DispatcherServlet,用于拦截所有的请求,并在初始化参数里配置application.properties文件的路径
DispatcherServlet
com.framework.servlet.DispatcherServlet
contextConfigLocation
application.properties
1
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern> </servlet-mapping>
application.properties文件中只填写包扫描路径即可
scanPackage=com.demo
创建各种注解
@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.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {String value() default "";
}
@Target({ ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {String value() default "";
}
@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {String value() default "";
}
创建一组业务处理逻辑,用DemoController接收请求并返回DemoService的处理结果给浏览器
@Controller
@RequestMapping(“/demo”)
public class DemoController {@Autowired
DemoService demoService;
@RequestMapping("/query")
public void query(HttpServletRequest request, HttpServletResponse response, @RequestParam("name") String name) throws IOException {
String result= demoService.get(name);
response.getWriter().write(result);
}
}
public interface DemoService {
String get(String name);
}
@Service
public class DemoServiceImpl implements DemoService {@Override
public String get(String name) {
return "My name is " + name;
}
}
接下来是重中之重:构造DispatcherServlet类
我们将在DispatcherServlet的init方法中进行:
- 加载配置文件
- 扫描所有的类
- 实例化所有单例Bean并放到IOC容器中
- 完成依赖注入
- 初始化HandlerMapping容器
然后,因为本框架只对Get请求进行处理,所以只重写doGet方法。
具体过程如下:
创建DispatcherServlet成员变量:
//加载application.properties配置文件中的内容
private Properties contextConfig = new Properties();//保存扫描到的所有类名称
private ListclassNames = new ArrayList<>(); //IOC容器,保存所有Bean实例
private Mapioc = new HashMap<>(); //handlerMapping容器,保存URL和Method的映射关系
private MaphandlerMapping = new HashMap<>(); 在DispatcherServlet中重写init方法并实现以下方法:
@Override
public void init(ServletConfig config) {//加载配置文件
loadConfig(config.getInitParameter("contextConfigLocation"));
//包扫描
initScanner(contextConfig.getProperty("scanPackage"));
//实例化扫描到的类,并放入到IOC容器中
initBeans();
//完成依赖注入
initAutowired();
//建立所有URL和Controller中方法的对应关系,并保存到HandlerMapping容器中
initHandlerMapping();
}
实现loadConfig方法
private void loadConfig(String contextConfigLocation) {
//找到application.properties文件路径
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
//加载application.properties
try {
contextConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实现initScanner方法,扫描com.demo包以及子包的类,并将类的全限定名称保存起来,为initBean实例化这些类做准备;
private void initScanner(String scanPackage) {
//扫描com.demo包以及子包的类
URL url = getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
File classPath = new File(url.getFile());
for (File file: classPath.listFiles()) {
if (file.isDirectory()) {
initScanner(scanPackage + "." +file.getName());
} else {
if (!file.getName().endsWith(".class")) continue;
String className = scanPackage + "." + file.getName().replace(".class", "");
classNames.add(className);
}
}
}
通过遍历classNames中的路径名称,并以反射的方法实例化带有@Controller和@Service注解的类,最后把实例加入到IOC容器中;
private void initBeans() throws Exception {
if (classNames.isEmpty()) return;
for (String className: classNames) {
Class<?> clazz = Class.forName(className);
//加了注解的类才能初始化
if (clazz.isAnnotationPresent(Controller.class)) {
Object instance = clazz.newInstance();
ioc.put(clazz.getSimpleName(), instance);
} else if (clazz.isAnnotationPresent(Service.class)) {
Object instance = clazz.newInstance();
for (Class<?> i : clazz.getInterfaces()) {
if (ioc.containsKey(i.getName())) {
throw new RuntimeException(i.getName() + "已存在");
}
ioc.put(i.getName(), instance);
}
} else {
continue;
}
}
}
实现initAutowired方法,遍历IOC容器中所有的实例,找到实例中带有@Autowire注解的成员变量,这些成员变量都是对象的引用,还没用进行赋值,所以用反射的方法将这些变量进行引用赋值。
private void initAutowired() throws IllegalAccessException {
if (ioc.isEmpty()) return;
for (Map.Entry<String, Object> entry: ioc.entrySet()) {
//获取被@Autowire注解的字段
Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
for (Field field: declaredFields) {
if (!field.isAnnotationPresent(Autowired.class)) continue;
//根据变量类型获取类路径
String beanName = field.getType().getName();
//设置暴力访问
field.setAccessible(true);
//将变量所注入的类和根据类路径查找到的实例设置给这个变量
field.set(entry.getValue(), ioc.get(beanName));
}
}
}
初始化HandlerMapping,它是SpringMVC中完成URL到Controller映射的组件,实质上是一个Map,容器初始化会建立所有URL和Controller中方法的对应关系,保存到HandlerMapping
中,用户请求时根据请求的URL快读定位到Controller中的某个方法。确定了方法后,就把请求参数绑定到方法的形参上。 private void initHandlerMapping() {
if (ioc.isEmpty()) return;
for (Map.Entry<String, Object> entry: ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(Controller.class)) continue;
//保存类上面的@RequestMapping("/demo")
String baseUrl = "";
if (clazz.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
baseUrl = requestMapping.value();
}
//获取方法上的Url
for (Method method: clazz.getMethods()) {
if (!method.isAnnotationPresent(RequestMapping.class)) continue;
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String fullUrl = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
//把URL和对应的方法放到HandlerMapping容器中
handlerMapping.put(fullUrl, method);
}
}
}
重写doGet方法,将请求转发给doDispatch方法进行处理
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
//handlerMapping中查到请求的URL,没有就返回404
if (!handlerMapping.containsKey(url)) {
resp.getWriter().write("404 Not Found");
return;
}
//通过url找到对应的方法,用反射的方式知道方法对应的实例对象,并执行这个方法
Method method = handlerMapping.get(url);
Map<String, String[]> parameterMap = req.getParameterMap();
String beanName = method.getDeclaringClass().getSimpleName();
method.invoke(ioc.get(beanName), req, resp, parameterMap.get("name")[0]);
}
通过以上步骤,我们完成了简易版SpringMVC框架的构建,下面看看运行结果:
- 访问路径为http://localhost:8080/mvc\_write\_war/时,返回404
- 访问路径为http://localhost:8080/mvc\_write\_war/demo/query?name=peter
还没有评论,来说两句吧...