分六步实现手写spring-mvc框架与DI依赖注入

 

springmvc主要包装了servlet,使每次的新添请求不用到web.xml里面去手动加,主要解决的问题就是把请求路径和开发写的controller层方法绑定起来。

最近看了一波spring-mvc的原理解析视频,大概理解一一下springmvc的大致工作原理和工作流程。为了加深对spring-mvc的理解,自己也手写了一波spring-mvc。

其实核心的东西就几点,把请求的url和方法上的自定义注解值匹配起来,通过反射获取方法的参数,在dispatchservlet里面处理HttpservletRequest,把响应的参数获取出来,然后method.invoke(controller实体,参数)返回一个结果,最后把结果放到HttpServletResponse里面。

首先像开发spring-mvc的一样,写好web.xml文件,controller文件,service文件等......

web.xml(入口)配置自己servlet,这时就不要写spring的DispatchServlet啦。(自信点直接写自己的类,配置好了servlet再新建文件从零写)

<!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>
  <servlet>
      <servlet-name>spring-mvc</servlet-name>
      <servlet-class>cn.wzy.servlet.WzyDispatchServlet</servlet-class>
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>config.properties</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
  </servlet>
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

这里的配置文件就是相当的简单,主要properties解析比xml解析更方便。

config.properties就如此的简单,相当于只是设置了扫描的包就是cn.wzy下。

package=cn.wzy

controller,service文件(像普通开发时套用spring一样,唯一的差别就是所有都用自己的注解)

controller层:

package cn.wzy.controller;

import cn.wzy.annotation.WzyAutowired;
import cn.wzy.annotation.WzyController;
import cn.wzy.annotation.WzyRequestMapping;
import cn.wzy.annotation.WzyRequestParam;
import cn.wzy.service.UserService;
import com.alibaba.druid.support.json.JSONUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:17
 */
@WzyController
@WzyRequestMapping("/user")
public class UserController {

	@WzyAutowired
	private UserService service;

	@WzyRequestMapping("/hello.do")
	public String hello(@WzyRequestParam("id") Integer id,
	                    @WzyRequestParam("name") String name) throws IOException {
		return service.sayHello(id,name);
	}

	@WzyRequestMapping("/hello2.do")
	public Object hello2(@WzyRequestParam("id") Integer id,
	                    @WzyRequestParam("name") String name) throws IOException {
		Map<String,Object> datas = new HashMap<>();
		datas.put("id",id);
		datas.put("name",name);
		return JSONUtils.toJSONString(datas);
	}

}

service层:

package cn.wzy.service;

/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:21
 */
public interface UserService {

	String sayHello(Integer id,String name);
}

service实现类:

package cn.wzy.service.impl;

import cn.wzy.annotation.WzyService;
import cn.wzy.service.UserService;

/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:21
 */
@WzyService
public class UserServiceImpl implements UserService {
	@Override
	public String sayHello(Integer id, String name) {
		return "hello " + id + " : " + name ;
	}
}

接下来就自己定义注解了:

/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:18
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface WzyAutowired {

	String value() default "";

}
/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:18
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WzyController {

	String value() default "";

}
/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:18
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface WzyRequestMapping {

	String value() default "";

}
/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:18
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface WzyRequestParam {

	String value() default "";

}
/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 14:18
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WzyService {

	String value() default "";

}

上面的所有步骤都是正常使用spring-mvc的时候的操作,下面就要开始自己手动实现spring-mvc的核心文件啦

第一步:我们需要加载配置文件,也就是上面的config.properties(只有一个扫描路径的参数项);

第二步:开始扫描所有的类文件;

第三步:对于每一个类,检查是否需要实例化(检查是否有注解申明);

第四步:对于每一个生成的示例,往有注解申明的字段上注入对象;

第五步:把controller层注解的类解析一下,把请求url和请求方法记录下来;

第六步:访问时解析路径,反射调用方法;

核心代码:具体工程代码github地址:https://github.com/1510460325/springframework

package cn.wzy.servlet;

import cn.wzy.annotation.*;

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.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author wzy 不短不长八字刚好.
 * @since 2018/9/2 13:58
 */
public class WzyDispatchServlet extends HttpServlet {

	private Properties properties = new Properties();

	/**
	 * 扫描的所有类名
	 */
	private List<String> classNames = new ArrayList<>();

	/**
	 * ioc实例化容器
	 */
	private Map<String,Object> ioc = new HashMap<>();

	/**
	 * url路径
	 */
	private Map<String,Handler> handlerMapping = new HashMap<>();

	@Override
	public void init(ServletConfig config) throws ServletException {
		//1、加载配置文件
		loadConfig(config.getInitParameter("contextConfigLocation"));
		//2、扫描文件
		scanner(properties.getProperty("package"));
		//3、初始化示例
		instance();
		//4、自动注入
		autowired();
		//5、初始化handlerMapping
		initHandlerMapping();
	}

	/**
	 * 初始化请求绑定
	 */
	private void initHandlerMapping() {
		for (Object value: ioc.values()) {
			Class<?> clazz = value.getClass();
			if (!clazz.isAnnotationPresent(WzyController.class)) {
				continue;
			}
			String baseUrl = "";
			if (clazz.isAnnotationPresent(WzyRequestMapping.class)) {
				WzyRequestMapping url = clazz.getAnnotation(WzyRequestMapping.class);
				if (!url.value().equals("")) {
					baseUrl += url.value();
				}
			}

			Method[] methods = clazz.getDeclaredMethods();
			for (Method method: methods) {
				if (!method.isAnnotationPresent(WzyRequestMapping.class)) {
					continue;
				}
				WzyRequestMapping url = method.getAnnotation(WzyRequestMapping.class);
				Handler handler = new Handler();
				handler.controller = value;
				handler.method = method;
				handler.url = baseUrl + url.value();
				handlerMapping.put(handler.url,handler);
			}
		}
	}

	/**
	 * 依赖注入
	 */
	private void autowired() {
		if (ioc.isEmpty()) {
			return;
		}
		for (Map.Entry<String,Object> entry: ioc.entrySet()) {
			Field[] fields = entry.getValue().getClass().getDeclaredFields();
			for (Field field: fields) {
				if (!field.isAnnotationPresent(WzyAutowired.class)) {
					continue;
				}

				WzyAutowired autowired = field.getAnnotation(WzyAutowired.class);
				String beanName = autowired.value();
				if (beanName.equals("")) {
					beanName = lowerFirst(field.getType().getName());
				}
				field.setAccessible(true);
				try {
					field.set(entry.getValue(),ioc.get(beanName));
				} catch (IllegalAccessException e) {
					e.printStackTrace();
					System.out.println("===Autowired " + field.getName() + " failed=====");
				}
			}
		}
	}

	/**
	 * 实例化
	 */
	private void instance() {
		if (classNames.isEmpty())
			return;
		try {
		for (String className: classNames) {
				Class<?> clazz = Class.forName(className);
				if (clazz.isAnnotationPresent(WzyController.class)) {
					String beanName = lowerFirst(clazz.getSimpleName());
					ioc.put(beanName,clazz.newInstance());
				} else if (clazz.isAnnotationPresent(WzyService.class)) {
					WzyService service = clazz.getAnnotation(WzyService.class);
					String beanName = service.value();
					if (beanName.equals("")) {
						beanName = lowerFirst(clazz.getSimpleName());
					}
					Object instance = clazz.newInstance();
					ioc.put(beanName,instance);

					Class<?>[] interfaces = clazz.getInterfaces();
					for (Class<?> c: interfaces) {
						ioc.put(lowerFirst(c.getName()),instance);
					}
				}
			}
		}catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
	}

	private String lowerFirst(String className) {
		char[] ch = className.toCharArray();
		ch[0] += 32;
		return new String(ch);
	}

	/**
	 * 递归扫描包下所有的类,存到list里面待下阶段生成实例
	 * @param packageName
	 */
	private void scanner(String packageName) {
		URL url = this.getClass().getClassLoader().getResource("/" + packageName.replace(".","/"));
		File classDir = new File(url.getFile());
		for (File file: classDir.listFiles()) {
			if (file.isDirectory()) {
				scanner(packageName + "." + file.getName());
			} else {
				classNames.add(packageName + "." + file.getName().replace(".class",""));
			}
		}
	}

	/**
	 * 加载配置文件
	 * @param location
	 */
	private void loadConfig(String location) {
		InputStream is = this.getClass().getClassLoader().getResourceAsStream(location);
		try {
			properties.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (is != null) {
					is.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * handlerMapping处理类
	 */
	private class Handler{
		protected Object controller;
		protected Method method;
		protected String url;

		public Handler() {
		}
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		this.doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doDispatcher(req,resp);
	}

	protected void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
		//获取请求url
		String url = req.getRequestURI();
		url = url.replace(req.getContextPath(),"");
		//映射到具体实例方法
		Handler handler = handlerMapping.get(url);
		Method targetMethod = handler.method;
		//绑定请求参数到具体方法
		Class<?>[] types = targetMethod.getParameterTypes();
		Object[] params = new Object[types.length];
		int index = 0;
		Annotation[][] annotations = targetMethod.getParameterAnnotations();
		for (Annotation[] an1: annotations) {
			for (Annotation an: an1) {
				if (an instanceof WzyRequestParam) {
					String paramName = ((WzyRequestParam)an).value();
					Class<?> type = types[index];
					if (type == Integer.class)
						params[index] = Integer.parseInt(req.getParameter(paramName));
					else
						params[index] = req.getParameter(paramName);
				}
				index++;
			}
		}
		try {
			Object result = targetMethod.invoke(handler.controller,params);
			resp.setContentType("application/json");
			resp.setCharacterEncoding("utf-8");
			resp.getWriter().write(result.toString());
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值