在手些SpringMVC之前,先明确SpringMVC的内容有那些:
在实现SpringMVC的过程中,需要实例化业务类(如Controller, Service ),此处通过注解进行实例化
所以需要创建一些注解:
首先:
实例化对象的注解有:
@Controller
@Service
自动装配置的注解有
@AutoWired
为方法配置访问名的注解:
@ResultMapping
获取参数名的注解:
@Param:
@Target(value={ElementType.TYPE}) // 只能作用在类上
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented // 生成文档
public @interface Controller {
}
@Target(value={ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
}
@Target(value= {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoWired {
}
@Target(value= {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value(); // 存放方法或类的地址名
}
@Target(value={ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Param {
String value(); // 存放参数的名称
}
以上准备做好了,下面开始手写SpringMVC;以下分两步进行手写:
写IOC容器之前,先明白三个问题
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>MySpringmvcdisplay-name>
<welcome-file-list>
<welcome-file>index.htmlwelcome-file>
<welcome-file>index.htmwelcome-file>
<welcome-file>index.jspwelcome-file>
<welcome-file>default.htmlwelcome-file>
<welcome-file>default.htmwelcome-file>
<welcome-file>default.jspwelcome-file>
welcome-file-list>
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>com.young.spring.core.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>source.propertiesparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name><u>springmvcu>servlet-name>
<url-pattern>*.actionurl-pattern>
servlet-mapping>
web-app>
标签中的标签中的内容是配置文件名,通过解析文件获取这个文件中的内容,这个配置文件中的内容是业务类所在的包。创建配置文件:source.properties
basePackage=com.young.business
标签中的内容是前端控制器的完全限定名,前端控制器是Spring的核心类;它的作用是解析配置文件,得到配置文件中的包的信息,再扫描这个包,得到所有的类,把这些加了@Controller或@Servlet注解的类全部实力化,并放在一个Map集合中,从而实现了IOC容器;
当然实现了IOC容器还有很多其他的功能,获得了IOC容器,就要创建HandlerMapping对象,即访问控制器的花名册,这个对象是一个Map集合,可通过url地址获得值,这个值是url访问的目标方法。以上功能是这一步需要实现的功能;
首先我们来实现这一步需要实现的功能
作为一个Servlet,DispatcherServlet必须先继承HttpServlet类;
请求发送过来后,首先访问的是它的init方法,所以先重写HttpServlet类的init方法;
@Override
private static final String BASE_PACKAGE = "basePackage";
public void init(ServletConfig config) throws ServletException {
// 1. 扫描并读取配置文件
doFile(config.getInitParameter(CONTEXT_CONFIGLOCATION));
// 2. 扫描用户设定的包下面的所有的类
doScanner(prop.getProperty(BASE_PACKAGE));
// 3. 根据className去实例化
try {
doInstance();
} catch (Exception e) {
e.printStackTrace();
}
// 4. 自动装配
try {
doAutoWired();
} catch (Exception e) {
e.printStackTrace();
}
// 5. 初始化HandlerMapping,解析controller里面的@RequestMapping注解
initHandlerMapping();
}
init方法需要扫描并读取配置文件,这个配置文件存放的是业务类所在的包,可通过web.xml中获取,通过标签中的值获取,获取方式:下面写init方法的,扫描并读取配置文件的方法 doFile(config.getInitParameter(CONTEXT_CONFIGLOCATION));即解析source.properties,并获取到他的内容,放入Properties集合中,properties集合中放的就是业务类所在的包名,通过包名可以获取到它里面所有的类
// 全局定义标签中的值
private static final String CONTEXT_CONFIGLOCATION = "contextConfigLocation";
// 使用properties存放配置文件的内容
private Properties prop = new Properties();
private void doFile(String configLocation) {
InputStream in = DispatcherServlet.class.getClassLoader().getResourceAsStream(configLocation);
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}
业务类所在的包存在properties中了,接下来我们要找到这个包下所有的类,创建一个List集合
// 存放完全限定名的集合
private List<String> paths = new ArrayList<>();
private void doScanner(String basePackage) {
String newPath = basePackage.replaceAll("\\.", "/");
// resource 的值为file:/F:/WH_JAVA_190328/MySQL/apache-tomcat-8.5.41/webapps/MySpringmvc/WEB-INF/classes/com/young/business/
URL resource =DispatcherServlet.class.getClassLoader().getResource(newPath);
String filePath = resource.getFile();
File file = new File(filePath);
if(file.exists()){
File[] listFiles = file.listFiles();
for (File file2 : listFiles) {
if(file2.isDirectory()) {
doScanner(basePackage + "." + file2.getName());
}else {
// clazz是类名
String clazz = file2.getName().replace(".class", "");
// 包名+类名就组成了这个类的完全限定名
paths.add(basePackage +"." + clazz);
}
}
}
}
获取到所有的类完全限定名后,我们要扫描里面所有的被@Controller和@Service注释的类,并为他们创建对象,并把这些对象以及对象的注解类型放入一个HandlerObject对象中,再以对象名为键,HandlerObject对象为值放入Map集合中,一个IOC容器就形成了
public class HandlerObject {
private Object instance;
private String instanceType;
public HandlerObject() {
super();
// TODO Auto-generated constructor stub
}
public HandlerObject(Object instance, String instanceType) {
super();
this.instance = instance;
this.instanceType = instanceType;
}
public Object getInstance() {
return instance;
}
public void setInstance(Object instance) {
this.instance = instance;
}
public String getInstanceType() {
return instanceType;
}
public void setInstanceType(String instanceType) {
this.instanceType = instanceType;
}
@Override
public String toString() {
return "HandlerObject [instance=" + instance + ", instanceType=" + instanceType + "]";
}
}
private Map<String,HandlerObject> ioc = new HashMap<>();
private void doInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if(!paths.isEmpty()) {
for (String className : paths) {
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(Controller.class)) {
Object newInstance = clazz.newInstance();
// 获取这个对象的类名
String simpleName = newInstance.getClass().getSimpleName();
// 将这个类的类名进行首字母小写处理成对象名
ioc.put(toLowerFirstWord(simpleName), new HandlerObject(newInstance, BeanType.CONTROLLER.name()));
}else if(clazz.isAnnotationPresent(Service.class)) {
Object newInstance = clazz.newInstance();
String simpleName = newInstance.getClass().getSimpleName();
ioc.put(toLowerFirstWord(simpleName), new HandlerObject(newInstance, BeanType.CONTROLLER.name()));
}
}
}
}
/**
* 把字符串的首字母小写
*
* @param name
* @return
*/
private String toLowerFirstWord(String name) {
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
创建完对象后,扫描这些对象,找出所有的被@AutoWired注释的对象,并为其赋值
private void doAutoWired() throws InstantiationException, IllegalAccessException {
if(!ioc.isEmpty()) {
for (HandlerObject handler : ioc.values()) {
Object instance = handler.getInstance();
// 获取对象中的所有属性
Field[] fields = instance.getClass().getDeclaredFields();
if(null != fields && fields.length > 0) {
for (Field field : fields) {
// 判断属性上是否含有@AutoWired,
if(field.isAnnotationPresent(AutoWired.class)) {
// 如果有,则获取属性名
String name = field.getName();
// 判断IOC容器中是否有这个对象名对应的对象
if(ioc.containsKey(name)) {
// 有,则给自己赋值
field.setAccessible(true);
field.set(instance, ioc.get(name).getInstance());
}else {
// 没有,则打印异常
System.err.println("IOC容器里面没有"+name+"对应的象");
}
}
}
}
}
}
}
创建HandlerMapping对象,将url值为键,HandlerMapping对象为值存入一个Map集合中,这个集合即花名册,往后可以通过url获取HandlerMapping对象,实现对目标方法的调用和赋值。以下我们来创建这个花名册:
public class HandlerMapping {
private Object instance;
private Method method;
public HandlerMapping() {
super();
// TODO Auto-generated constructor stub
}
public HandlerMapping(Object instance, Method method) {
super();
this.instance = instance;
this.method = method;
}
public Object getInstance() {
return instance;
}
public void setInstance(Object instance) {
this.instance = instance;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "HandlerMapping [instance=" + instance + ", method=" + method + "]";
}
}
private Map<String,HandlerMapping> handlerMapping = new HashMap<>();
private void initHandlerMapping() {
if(!ioc.isEmpty()) {
for (HandlerObject handler : ioc.values()) {
Object instance = handler.getInstance();
String path = "";
// 判断类上是否有@RequestMapping注解,有则获取它的值
if(instance.getClass().isAnnotationPresent(RequestMapping.class)) {
RequestMapping declaredAnnotation = instance.getClass().getDeclaredAnnotation(RequestMapping.class);
path += "/" + declaredAnnotation.value();
}
// 获取这个类上的所有方法
Method[] declaredMethods =instance.getClass().getDeclaredMethods();
if(null != declaredMethods && declaredMethods.length > 0) {
for (Method method : declaredMethods) {
// 判断这个方法是否有@RequestMapping注解,有则获取它的值
if(method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping declaredAnnotation2 = method.getDeclaredAnnotation(RequestMapping.class);
// 拼接路径
path += "/" + declaredAnnotation2.value();
HandlerMapping handlerMapping2 = new HandlerMapping();
handlerMapping2.setInstance(instance);
handlerMapping2.setMethod(method);
handlerMapping.put(path, handlerMapping2);
}
}
}
}
}
}
以上第一步就完成了,接下来就是接收请求,并处理请求
前端向后端发送请求,会先访问它的service方法,service方法在通过解析请求把它发送给doGet方法或doPost方法,这里为简化,把所有的请求都使用doPost方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
如何接收请求并处理请求呢?可分为以下几个步骤:
private Object doDispatch(HttpServletRequest req,
HttpServletResponse resp) {
String requestURI = req.getRequestURI();
// 处理URI
String fileName = req.getServletContext().getContextPath();
requestURI = requestURI.replaceAll(fileName, "");
requestURI = requestURI.substring(0, requestURI.lastIndexOf("."));
// 判断是否存在这个方法
if(handlerMapping.containsKey(requestURI)) {
HandlerMapping handler = handlerMapping.get(requestURI);
Method method = handler.getMethod();
Object instance = handler.getInstance();
// 获取传递的参数值
Map<String, String[]> parameterMap =
req.getParameterMap();
// 获取参数列表
Parameter[] parameters = method.getParameters();
// 获取参数的所有注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 创建存放参数值的数组
Object[] param = new Object[parameters.length];
int index = 0;
// 解析注解,通过注解的值获取传过来的参数值,并放入参数值的数组中
for (Annotation[] annotations : parameterAnnotations) {
for (Annotation annotation : annotations) {
if(annotation instanceof Param) {
Param anno = (Param)annotation;
String value = anno.value();
// 判断value值
if(value.equals("request")) {
param[index] = req;
}else if(value.equals("response")) {
param[index] = resp;
}else {
String[] strings = parameterMap.get(value);
param[index] = strings[0];
}
}
}
index++;
}
// 给method赋值
try {
Object invoke = method.invoke(instance, param);
return invoke;
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
处理请求后要给前端发送响应,接收方法的返回值,判断他返回的是字符串还是ModelAndView。再判断它是内部转发还是重定向,再执行相应的操作
首先创建ModelAndView类
public class ModelAndView {
private String viewName;
// data中存放的是放在作用域中的值
private static Map<String,Object> data = new HashMap<>();
// 实现ModelAndView的addAttribute方法
public void addAttribute(String name, Object obj) {
data.put(name, obj);
}
public ModelAndView() {
super();
}
public ModelAndView(String viewName) {
super();
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
}
处理响应
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object obj = doDispatch(req,resp);
if(null != obj) {
if(obj instanceof String ) {
// 判断是否是重定向
if(obj.toString().startsWith("redirect:")) {
resp.sendRedirect(obj.toString());
}else {
req.getRequestDispatcher(obj.toString()).forward(req, resp);
}
}else if(obj instanceof ModelAndView) {
ModelAndView model = (ModelAndView) obj;
String viewName = model.getViewName();
// 判断是否是重定向
if(viewName.toString().startsWith("redirect:")) {
resp.sendRedirect(viewName.toString());
}else {
Set<Entry<String, Object>> entrySet = model.getData().entrySet();
for (Entry<String, Object> entry : entrySet) {
req.setAttribute(entry.getKey(), entry.getValue());
}
req.getRequestDispatcher(viewName.toString()).forward(req, resp);
}
}
}
}
把以上所有的方法组合在一个DispatcherServlet类中,这样就创建好了SpringMVC的一个核心类