简单手写SpringMvc框架,理解IOC容器

本文介绍了一位新手尝试手写SpringMvc框架的过程,重点在于理解IOC容器的工作原理。通过创建maven项目,定义注解,编写Servlet类,详细阐述了实例化、注入和URL映射的实现,包括注解的使用、类的扫描、反射机制以及依赖注入等关键步骤。
摘要由CSDN通过智能技术生成

本人理解,SpringMvc重要的3点:实例化、注入、URL映射,新手,有误莫怪。

简单理解就是扫描用户设定的包下面所有的类,然后进行遍历,对加了@Controller、@Service注解的对象进行实例化,然后对这些类里面的加了@Autowired注解的属性进行依赖注入,对Controller中加了@RequestMapping注解的方法做URL映射。

1、创建一个maven项目

Packaging选择war,因为我们是一个web工程。

然后JDK版本选择1.8,因为java1.8是支持从反射获取参数名的。

下图为项目结构


2、定义注解

只是为了理解,所以功能不是特别强大,本项目只定义了5个比较常用的注解Controller、Service、Autowire、RequestMapping、RequestParam,便于区分,名称前面加上Sunwj。废话不多说,直接上代码。

package com.sunwj.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjController {

	String value();

}
package com.sunwj.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjService {

	String value();

}
package com.sunwj.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjAutowire {

	String value();

}
package com.sunwj.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjRequestMapping {

	String value();

}
package com.sunwj.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjRequestParam {

	String value();

}

@Target、@Retention、@Documented为Java自定义注解,起辅助作用,不会影响代码的实际逻辑。

2.1、@Target:定义注解的作用目标

@Target(ElementType.TYPE)    // 接口、类、枚举、注解

@Target(ElementType.FIELD)  // 字段、枚举的常量

@Target(ElementType.METHOD)  // 方法

@Target(ElementType.PARAMETER)  // 方法参数

@Target(ElementType.CONSTRUCTOR)   // 构造函数

@Target(ElementType.LOCAL_VARIABLE) // 局部变量

@Target(ElementType.ANNOTATION_TYPE) // 注解

@Target(ElementType.PACKAGE) / // 包    

查看Target源码可以知道, ElementType 可以有多个,一个注解可以为类的,方法的,字段的等等。

2.2、@Retention: 定义注解的保留策略

@Retention(RetentionPolicy.SOURCE)    //注解仅存在于源码中,在class字节码文件中不包含

@Retention(RetentionPolicy.CLASS)       // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,

@Retention(RetentionPolicy.RUNTIME)   // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

2.3、@Document:说明该注解将被包含在javadoc中

3、编写对应的Servlet类

SpringMvc的核心类DispatcherServlet。我们不需要那么多功能。本项目就只涉及到实例化、注入、URL映射。

创建一个Servlet类,SunwjServlet。

在web.xml声明SunwjServlet并映射。

<servlet>
  <servlet-name>sunwjServlet</servlet-name>
  <servlet-class>com.sunwj.servlet.SunwjServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>application.properties</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>sunwjServlet</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

在初始化方法里面,分为5个步骤:

3.1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation"));

通过web.xml配置的参数,获得配置文件,进行加载。

3.2、初始化所有相关联的类,扫描用户设定的包下面所有的类 doScanner(properties.getProperty("scanPackage"));

在Properties中,定义了一个scanPackage为扫描的路径。通过递归,获取该路径下的所有文件。

3.3、拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean)  doInstance();

对扫描到的所有类,进行遍历,对加了@SunwjController、@SunwjService注解的对象进行实例化,并存入Map中。

3.4、初始化HandlerMapping(将url和method对应上) initHandlerMapping();

对实例化的对象进行遍历,获取加了@SunwjController注解的对象的method数值,然后进行遍历,对加了@SunwjRequestMapping注解的方法,进行映射,存入URL映射的Map中。

3.5、实现注入 ioc();

对实例化的对象进行遍历,获取加了@SunwjController注解的对象的field数值,然后进行遍历,对加了@SunwjAutowire注解的属性,进行依赖注入。

这样就完成了本项目最重要的功能了,接下去写一下doGet、doPost方法。

代码如下:

package com.sunwj.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import com.sunwj.annotation.SunwjAutowire;
import com.sunwj.annotation.SunwjController;
import com.sunwj.annotation.SunwjRequestMapping;
import com.sunwj.annotation.SunwjRequestParam;
import com.sunwj.annotation.SunwjService;

public class SunwjServlet extends HttpServlet {

	private static final long serialVersionUID = 7265511195544348690L;

	private Properties properties = new Properties();
	
	private List<String> classNames = new ArrayList<String>();
	
	private Map<String, Object> instanceMap = new HashMap<String, Object>();

	private Map<String, Method> handlerMapping = new HashMap<String, Method>();


	@Override
	public void init(ServletConfig config) {

		// 1.加载配置文件
		doLoadConfig(config.getInitParameter("contextConfigLocation"));
		// 2.初始化所有相关联的类,扫描用户设定的包下面所有的类
		doScanner(properties.getProperty("scanPackage"));
		// 3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean)
		doInstance();
		// 4.初始化HandlerMapping(将url和method对应上)
		initHandlerMapping();
		// 5.实现注入
		ioc();

	}

	private void doLoadConfig(String location) {
		// 把web.xml中的contextConfigLocation对应value值的文件加载到流里面
		InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
		try {
			// 用Properties文件加载文件里的内容
			properties.load(resourceAsStream);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 关流
			if (null != resourceAsStream) {
				try {
					resourceAsStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private void doScanner(String packageName) {
		// 把所有的.替换成/
		URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
		System.out.println(url);
		File dir = new File(url.getFile());
		for (File file : dir.listFiles()) {
			if (file.isDirectory()) {
				// 递归读取包
				doScanner(packageName + "." + file.getName());
			} else {
				String className = packageName + "." + file.getName().replace(".class", "");
				classNames.add(className);
			}
		}
	}
	
	private void doInstance() {
		if (classNames.isEmpty()) {
			return;
		}
		for (String className : classNames) {
			try {
				// 把类搞出来,反射来实例化(只有加@SunwjController需要实例化)
				Class<?> clazz = Class.forName(className);
				if (clazz.isAnnotationPresent(SunwjController.class)) {
					instanceMap.put(clazz.getAnnotation(SunwjController.class).value(), clazz.newInstance());
				} else if (clazz.isAnnotationPresent(SunwjService.class)) {
					instanceMap.put(clazz.getAnnotation(SunwjService.class).value(), clazz.newInstance());
				}else {
					continue;
				}
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}
		}
	}
	
	private void initHandlerMapping() {
		if (instanceMap.isEmpty()) {
			return;
		}
		try {
			for (Entry<String, Object> entry : instanceMap.entrySet()) {
				Class<? extends Object> clazz = entry.getValue().getClass();
				if (!clazz.isAnnotationPresent(SunwjController.class)) {
					continue;
				}

				// 拼url时,是controller头的url拼上方法上的url
				String baseUrl =  clazz.getAnnotation(SunwjController.class).value();
				
				Method[] methods = clazz.getMethods();
				for (Method method : methods) {
					
					if (!method.isAnnotationPresent(SunwjRequestMapping.class)) {
						continue;
					}
					
					SunwjRequestMapping sunwjRequestMapping = method.getAnnotation(SunwjRequestMapping.class);
					String url = sunwjRequestMapping.value();

					url = (baseUrl + "/" + url).replaceAll("/+", "/");
					handlerMapping.put(url, method);
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private void ioc() {
		if (instanceMap.isEmpty())
			return;
		for (Map.Entry<String, Object> entry : instanceMap.entrySet()) {
			// 拿到里面的所有属性
			Field fields[] = entry.getValue().getClass().getDeclaredFields();
			for (Field field : fields) {
				try {
					field.setAccessible(true);// 可访问私有属性
					if (field.isAnnotationPresent(SunwjAutowire.class)) {
						SunwjAutowire sunwjAutowire = field.getAnnotation(SunwjAutowire.class);
						String value = sunwjAutowire.value();
						field.set(entry.getValue(), instanceMap.get(value));
					}
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
	}

	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
		try {
			doDispatch(req, resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	
	public void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		if (handlerMapping.isEmpty()) {
			return;
		}

		String url = req.getRequestURI();
		String contextPath = req.getContextPath();

		url = url.replace(contextPath, "").replaceAll("/+", "/");

		if (!this.handlerMapping.containsKey(url)) {
			resp.getWriter().write("404 NOT FOUND!");
			return;
		}

		Method method = this.handlerMapping.get(url);
		// 获取方法里的参数
		Parameter[] parameters =  method.getParameters();
		
		// 保存参数值
		List<Object> paramValue = new ArrayList<Object>();
		
		for (Parameter parameter : parameters) {
			// 当前参数有别名注解并且别名不为空
            if(parameter.isAnnotationPresent(SunwjRequestParam.class) && !parameter.getAnnotation(SunwjRequestParam.class).value().isEmpty()){
                String value = req.getParameter(parameter.getAnnotation(SunwjRequestParam.class).value());
                paramValue.add(value);
            }else if (parameter.getParameterizedType().getTypeName().contains("HttpServletRequest")) {
                paramValue.add(req);
            }else if (parameter.getParameterizedType().getTypeName().contains("HttpServletResponse")) {
                paramValue.add(resp);
            }else{
                paramValue.add(null);
            }
		}

		// 利用反射机制来调用
		try {
			method.invoke(this.instanceMap.get('/' + url.split("/")[1]), paramValue.toArray());// 第一个参数是method所对应的实例 // 在ioc容器中
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

4、例子

创建controller,service及实现类。

package com.sunwj.controller;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

import com.sunwj.annotation.SunwjAutowire;
import com.sunwj.annotation.SunwjController;
import com.sunwj.annotation.SunwjRequestMapping;
import com.sunwj.annotation.SunwjRequestParam;
import com.sunwj.service.HelloService;

@SunwjController("/hello")
public class HelloContr {

	@SunwjAutowire("helloService")
	private HelloService helloService;
	
	@SunwjRequestMapping("/hello")
	public void hello(String name, HttpServletResponse resp) {
		try {
			String result = helloService.sayHello(name);
			PrintWriter out = resp.getWriter();
			out.write(result);
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	@SunwjRequestMapping("/sayHello")
	public void sayHello(@SunwjRequestParam("name") String n,@SunwjRequestParam("age") Integer a,  HttpServletResponse resp) {
		try {
			String result = helloService.sayHello(n);
			PrintWriter out = resp.getWriter();
			out.write(result + a + "岁了!");
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	
	}

}
package com.sunwj.service;

public interface HelloService {

	public String sayHello(String name);

}
package com.sunwj.service.impl;

import com.sunwj.annotation.SunwjService;
import com.sunwj.service.HelloService;

@SunwjService("helloService")
public class HelloServiceImpl implements HelloService{

	public String sayHello(String name) {
		return "hello," + name + "。";
	}

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值