题记:
为了解SpringMVC加载过程的细节,最近阅读了其部分源码,并自己手写实现了一个简单的SpringMVC框架,现记录作为总结。
分为三篇博客:
• 源码分析之Spring MVC上下文的加载细节(一)
• 源码分析之Spring MVC上下文的加载细节(二)
• 源码分析之动手实现手写一个自己的SpringMVC框架(三)【本篇】
阅读完这三篇博客,将了解到:
• Spring容器整套技术是如何与我们web容器的耦合?
• Spring MVC启动自己持有的MVC上下文之前需要IOC容器的支持,Spring是如何驱动IOC容器的初始化的细节的?
• Spring MVC上下文初始化以及组件和服务初始化细节?
• 完成一个属于的自己SpringMVC框架
1.代码地址
获取代码
(若链接失效,请用这个:https://github.com/hdonghong/dh-spingmvc)
2.简介和开发步骤
本次将开发一个基于配置和注解的SpringMVC框架,主要是对前两篇文章的总结和对SpringMVC更进一步的理解。
本次项目结构图如下:
开发步骤:
自定义注解【完成五个注解】
自定义工具类,用于解析mvc配置文件、转换类限定名为路径、转换类名首字母为小写等
自定义DispatcherServlet
测试
3.自定义注解
首先我们来了解一下元注解。
元注解,是用来解释我们的自定义注解。结合xml文档的dtd,schema约束。
@Target:指定注解可作用什么目标上,如在类、方法、变量上。
@Retention:描述注解的声明周期,SOURCE:源文件时期;CLASS:编译时期;RUNTIME:运行时期。
@Documented:在生成javadoc文档的时候将该Annotation也写入到文档中。
本次自定义的注解:
@DhController:标注bean类为controller层
@DhService:标注bean类为service层
@DhRequestMapping:映射请求地址
@DhQualifier:注入bean实例
@DhRequestParam:数据绑定后台控制层获取参数
注解实现代码:
@Target(ElementType.TYPE)// 指定直接可作用在接口、类、枚举、注解上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DhController {
/**
* 给controller起别名
* @return
*/
String value() default "";
}
@Target(ElementType.TYPE)//指定直接可作用在接口、类、枚举、注解上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DhService{
/**
*给service起别名
*@return
*/
String value() default"";
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DhRequestMapping{
String value() default "";
}
@Target(value={ElementType.ANNOTATION_TYPE,ElementType.FIELD,
ElementType.METHOD,ElementType.PARAMETER,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DhQualifier{
String value() default"";
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DhRequestParam{
/**
*表示参数的别名,必填
*@return
*/
Stringvalue();
}
4.DispatcherServlet的编写
步骤:
①通过web.xml拿到基本包信息 读外部配置文件
②全自动扫描基本包下的bean,加载Spring容器
③通过注解对象,找到每个bean,反射获取实例
④依赖注入,实现ioc机制
⑤handlerMapping通过基部署 和 基于类的url找到相应的处理器
具体实现代码:
public class DhDispatcherServlet extends HttpServlet {
// 集合全自动扫描基础包下面的类限定名
private List<String> beanNames = new ArrayList<>();
// 缓存 key/value: 类注解参数/类实例对象,存储controller和service实例
private Map<String, Object> instanceMaps = new HashMap<>();
// key/value: 请求url/handler的method
private Map<String, Method> handlerMaps = new HashMap<>();
// 再维护一个map,存储controller实例
private Map<String, Object> controllerMaps = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
try {
// 1.通过web.xml拿到基本包信息 读外部配置文件
String mvcConfig = config.getInitParameter("contextConfigLocation")
.replace("*", "")
.replace("classpath:", "");
String basePackName = CommonUtils.getBasePackName(mvcConfig);
System.out.println("扫描的基包是:" + basePackName);
// 2.全自动扫描基本包下的bean,加载Spring容器
scanPack(basePackName);
// 3.通过注解对象,找到每个bean,反射获取实例
reflectBeansInstance();
// 4.依赖注入,实现ioc机制
doIoc();
// 5.handlerMapping通过基部署 和 基于类的url找到相应的处理器
initHandlerMapping();
} catch (Exception e) {
e.printStackTrace();
if (e instanceof ServletException) {
new ServletException(e);
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理业务
* @param req
* @param resp
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if (handlerMaps.isEmpty()) {
return;
}
String uri = req.getRequestURI();// 如:/project_name/classURI/methodURI
String contextPath = req.getContextPath();// 如:/project_name
String url = uri.replace(contextPath, "").replaceAll("/+", "/");
// 获取到请求执行的方法
Method handlerMethod = handlerMaps.get(url);
PrintWriter out = resp.getWriter();
if (handlerMethod == null) {
out.print("404!!!您访问的资源不存在");
return;
}
// 获取方法的参数列表
Parameter methodParameters[] = handlerMethod.getParameters();
// 调用方法需要传递的形参
Object paramValues[] = new Object[methodParameters.length];
for (int i = 0; i < methodParameters.length; i++) {
if (ServletRequest.class.isAssignableFrom(methodParameters[i].getType())) {
paramValues[i] = req;
} else if (ServletResponse.class.isAssignableFrom(methodParameters[i].getType())) {
paramValues[i] = resp;
} else {// 其它参数,目前只支持String,Integer,Float,Double
// 参数绑定的名称,默认为方法形参名
String bindingValue = methodParameters[i].getName();
if (methodParameters[i].isAnnotationPresent(DhRequestParam.class)) {
bindingValue = methodParameters[i].getAnnotation(DhRequestParam.class).value();
}
// 从请求中获取参数的值
String paramValue = req.getParameter(bindingValue);
paramValues[i] = paramValue;
if (paramValue != null) {
if (Integer.class.isAssignableFrom(methodParameters[i].getType())) {
paramValues[i] = Integer.parseInt(paramValue);
} else if (Float.class.isAssignableFrom(methodParameters[i].getType())) {
paramValues[i] = Float.parseFloat(paramValue);
} else if (Double.class.isAssignableFrom(methodParameters[i].getType())) {
paramValues[i] = Double.parseDouble(paramValue);
}
}
}
}
try {
handlerMethod.invoke(controllerMaps.get(url), paramValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 通过对请求url分析之后拿到响应的handler实例里面的method处理
*/
private void initHandlerMapping() throws Exception {
if (instanceMaps.isEmpty()) {
throw new Exception("没有发现handler对象");
}
for (Map.Entry<String, Object> entry : instanceMaps.entrySet()) {
Class<?> aClass = entry.getValue().getClass();
// 通过实例区分Controller层对象
if (aClass.isAnnotationPresent(DhController.class)) {
// 实现注解映射请求路径,允许当controller类没有使用@DhRequestMapping注解时,
// 可使用@DhController注解的value作为请求路径
String classURI = "";
if (aClass.isAnnotationPresent(DhRequestMapping.class)) {
classURI = aClass.getAnnotation(DhRequestMapping.class).value();
} else {
classURI = aClass.getAnnotation(DhController.class).value();
}
// 遍历controller类中每个使用@DhRquestMapping的方法,细化请求路径
Method[] methods = aClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(DhRequestMapping.class)) {
String methodURI = method.getAnnotation(DhRequestMapping.class).value();
// 存入handlerMaps
String url = ("/" + classURI + "/" + methodURI).replaceAll("/+", "/");
handlerMaps.put(url, method);
// 再维护一个只存储controller实例的map
controllerMaps.put(url, entry.getValue());
}
}
}
}
}
/**
* 依赖注入,实现ioc机制
* @throws Exception
*/
private void doIoc() throws Exception {
if (instanceMaps.isEmpty()) {
throw new Exception("没有发现可注入的实例");
}
for (Map.Entry<String, Object> entry : instanceMaps.entrySet()) {
Field[] fields = entry.getValue().getClass().getDeclaredFields();
// 遍历bean对象的字段
for (Field field : fields) {
if (field.isAnnotationPresent(DhQualifier.class)) {
// 通过bean字段对象上面的注解参数来注入实例
String insMapKey = field.getAnnotation(DhQualifier.class).value();
if (insMapKey.equals("")) {// 如果使用@DhController,@DhService没有配置value的值,默认使用类名 首字母小写
insMapKey = CommonUtils.toLowerFirstWord(field.getType().getSimpleName());
}
field.setAccessible(true);
// 注入实例
field.set(entry.getValue(), instanceMaps.get(insMapKey));
}
}
}
}
/**
* 通过注解对象,找到每个bean,反射获取实例
*/
private void reflectBeansInstance() throws Exception {
if (beanNames.isEmpty()) {
return;
}
for (String className : beanNames) {
Class<?> aClass = Class.forName(className);
if (aClass.isAnnotationPresent(DhController.class)) {// 操作控制层的实例
Object controllerInstance = aClass.newInstance();
// 进一步对这个控制层实例对象打标签,维护到缓存中
DhController controllerAnnotation = aClass.getAnnotation(DhController.class);
String insMapKey = controllerAnnotation.value();
if ("".equals(insMapKey)) {// 如果使用@DhController,@DhService没有配置value的值,默认使用类名 首字母小写
insMapKey = CommonUtils.toLowerFirstWord(aClass.getSimpleName());
}
instanceMaps.put(insMapKey, controllerInstance);
} else if (aClass.isAnnotationPresent(DhService.class)) {// 操作业务层的实例
Object serviceInstance = aClass.newInstance();
// 进一步对这个业务层实例对象打标签,维护到缓存中
DhService serviceAnnotation = aClass.getAnnotation(DhService.class);
String insMapKey = serviceAnnotation.value();
if ("".equals(insMapKey)) {// 如果使用@DhController,@DhService没有配置value的值,默认使用类名 首字母小写
insMapKey = CommonUtils.toLowerFirstWord(aClass.getSimpleName());
}
instanceMaps.put(insMapKey, serviceInstance);
}
}
}
/**
* 扫描基本包
* @param basePackName
*/
private void scanPack(String basePackName) throws Exception {
URL url = this.getClass()
.getClassLoader()
.getResource("/" + CommonUtils.transferQualifiedToPath(basePackName));
// 读取到扫描包
File dir = new File(url.getFile());
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
// 遇到目录递归读取文件
scanPack(basePackName + "." + file.getName());
} else if (file.isFile()) {
// 将形如pers.hdh.controller.Xxx的类限定名字符串加入beanNames中
beanNames.add(basePackName + "." + file.getName().replace(".class", ""));
System.out.println("扫描到的类有:" + basePackName + "." + file.getName().replace(".class", ""));
}
}
}
}
5.测试
链接:https://github.com/hdonghong/dh-spingmvc/blob/master/README.md