原文链接:
https://mp.weixin.qq.com/s/36F_fFbGKkRL20DJgX4ahg
跟着大佬的推送做了一遍,自己加了一点注释。
基本思路是实现一个自己的DispatcherServlet,在容器启动时初始化,进行包扫描,获得controller注解的实例,并将url和各个方法进行对应。
比较简陋,只有controller,requestmapping,requestparam三个注解。
啊其实我挺希望大佬能加个autowired这类的注解的实现的。。。这样才像spring嘛。
直接上代码:
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wmx</groupId>
<artifactId>myspringmvc</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>myspringmvc Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<!-- 只在编译和测试时运行 -->
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>myspringmvc</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.properties:
scanPackage=com.wmx.core
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>MySpringServlet</servlet-name>
<servlet-class>com.wmx.servlet.MySpringServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MySpringServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
实现的三个注解:
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 MyController {
/**
* 默认值为""
* @return
*/
String value() default "";
}
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 MyRequestMapping {
String value() default "";
}
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 MyRequestParam {
String value();
}
servlet实现:
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
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.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wmx.annotation.MyController;
import com.wmx.annotation.MyRequestMapping;
public class MySpringServlet extends HttpServlet{
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<>();
private Map<String ,Object> ioc = new HashMap<>();//bean名称,实例对象的map
private Map<String , Method> handlerMapping = new HashMap<>();
private Map<String , Object> controllerMap = new HashMap<>();
@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) {
resp.getWriter().write("500 server exception");
e.printStackTrace();
}
}
/**
* 通过请求来查询对应的方法,并通过反射来进行调用
* @param req
* @param resp
* @throws Exception
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
if(handlerMapping.isEmpty())
return;
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
//将项目路径替换为空
String url = uri.replace(contextPath, "");
//如果没有对应方法,返回404
if(!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 not found!");
return;
}
//获得方法参数
Method m = this.handlerMapping.get(url);
Class<?>[] params = m.getParameterTypes();
//获取请求参数
Map<String , String[]> paramMap = req.getParameterMap();
//保存参数值
Object[] paramValues = new Object[params.length];
for(int i = 0 ; i < params.length ; i++) {
String paramName = params[i].getSimpleName();
if (paramName.equals("HttpServletRequest")){
//参数类型已明确,这边强转类型
paramValues[i]=req;
continue;
}
if (paramName.equals("HttpServletResponse")){
paramValues[i]=resp;
continue;
}
if(paramName.equals("String")){
for (Entry<String, String[]> param : paramMap.entrySet()) {
//去除中括号和逗号后的空白符
//字符串数组转换为字符串时会加上中括号和逗号
String value =Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i]=value;
}
}
//其他参数类型可自行扩展
}
//反射调用方法
m.invoke(this.controllerMap.get(url), paramValues);
}
@Override
public void init(ServletConfig config) throws ServletException {
//加载配置文件,从web.xml里读出contextConfigLocation参数的值
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//扫描类,获得类名,为下一步用反射实例化类做准备
doScanner(properties.getProperty("scanPackage"));
//实例化bean
doInstance();
//初始化handlermapping
initHandlerMapping();
}
/**
* 加载配置文件
* @param location
*/
private void doLoadConfig(String location) {
//从web.xml里获得properties文件的路径,然后通过类加载器从路径里加载配置文件的输入流
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
try {
//加载application.properties里的内容
properties.load(inputStream);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* 根据配置文件里的扫描包属性,扫描该目录下的所有类的全名并放入list中
* @param packageName
*/
private void doScanner(String packageName) {
//读取扫描包的路径,把.替换成/,然后通过file去读取
URL url = this.getClass().getClassLoader().getResource("/"+packageName.replace(".", "/"));
File dir = new File(url.getFile());
//递归遍历所有java文件信息
for(File f : dir.listFiles()) {
if(f.isDirectory()) {
//递归遍历目录
doScanner(packageName + "."+f.getName());
}else {
String className = packageName + "."+f.getName().replace(".class", "");
classNames.add(className);
}
}
}
/**
* 通过反射实例化类
*/
private void doInstance() {
if(classNames.isEmpty()) {
return;
}
for(String s : classNames) {
try {
Class<?> clazz = Class.forName(s);
//只把具有@MyController注解的类进行实例化
if(clazz.isAnnotationPresent(MyController.class)) {
Object o = clazz.newInstance();
String classShortName = clazz.getSimpleName();
String beanName = toLowerFirstWord(classShortName);//beanName默认首字母小写
//将bean名称和实例放入map中
ioc.put(beanName, o);
}else {
continue;
}
}catch(Exception e) {
e.printStackTrace();
//继续循环
continue;
}
}
}
/**
* 初始化各种controller及其中方法与具体url的对应关系
*/
private void initHandlerMapping() {
if(ioc.isEmpty()) {
return;
}
for(Entry<String, Object> e : ioc.entrySet()) {
Class<? extends Object> clazz = e.getValue().getClass();
if(!clazz.isAnnotationPresent(MyController.class))
continue;
//先拼接controller的url
String baseUrl = "";
if(clazz.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping m = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = m.value();
}
//遍历controller中方法,获得方法上的url值,和controller的值拼接后放入map中保存
Method[] methods = clazz.getMethods();
for(Method m : methods) {
if(!m.isAnnotationPresent(MyRequestMapping.class))
continue;
MyRequestMapping mrm = m.getAnnotation(MyRequestMapping.class);
String url = baseUrl + mrm.value();
handlerMapping.put(url, m);
controllerMap.put(url, e.getValue());
}
}
}
/**
* 把字符串的首字母小写
* @param name
* @return
*/
private String toLowerFirstWord(String name){
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
测试用controller:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wmx.annotation.MyController;
import com.wmx.annotation.MyRequestMapping;
import com.wmx.annotation.MyRequestParam;
@MyController
@MyRequestMapping("/test")
public class TestController {
@MyRequestMapping("/doTest")
public void test1(HttpServletRequest request, HttpServletResponse response,
@MyRequestParam("param") String param){
System.out.println(param);
try {
response.getWriter().write( "doTest method success! param:"+param);
} catch (IOException e) {
e.printStackTrace();
}
}
@MyRequestMapping("/doTest2")
public void test2(HttpServletRequest request, HttpServletResponse response){
try {
response.getWriter().println("doTest2 method success!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
将项目部署到tomcat上,启动访问
http://localhost:8080/myspringmvc/test/doTest?param=[xxx,qqq]
如果访问一个不存在的链接:
这个手写框架里大量使用了反射及注解相关的API,spring真正的实现应该也是大量地用到了这些吧。
还是很感谢大佬地推送的。