手写MVC框架
1. MVC框架执行的大致原理
2. 注解开发
自定义controller @MyController
/**
* 自定义controller注解
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default "";
}
自定义Service @MyService
/**
* 自定义service注解
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
String value() default "";
}
自定义RequestMapping @MyRequestMapping
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default "";
}
自定义Autowired @MyAutowired
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
String value() default "";
}
3. 自定义DispatcherServlet
public class MyDispatcherServlet extends HttpServlet {
// 读取properties文件
private Properties properties = new Properties();
// 存放所有class全限定类名
private List<String> classNames = new ArrayList<>();
// ioc 容器
private Map<String, Object> iocMap = new HashMap<>();
// 存放所有URL和method的映射
List<Handler> handlerList = new ArrayList<>();
@Override
public void init(ServletConfig config) throws ServletException {
// 1 加载配置文件 springmvc.properties
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
try {
doLoadConfig(contextConfigLocation);
} catch (IOException e) {
e.printStackTrace();
}
// 2 获取需要扫描的包下的所有class文件
doScan(properties.getProperty("scanPackage"));
// 3 初始化bean对象(实现ioc容器,基于注解)
doInstance();
// 4 实现依赖注入
doAutoWired();
// 5 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
initHandlerMapping();
System.out.println("mvc 初始化完成....");
// 等待请求进入,处理请求
}
/**
* 配置url和Method的映射关系
*/
private void initHandlerMapping() {
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
Object obj = entry.getValue();
// 先查看当前类上是否含有@MyRequestMapping注解
String baseMappingUrl = "";
if (obj.getClass().isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping annotation = obj.getClass().getAnnotation(MyRequestMapping.class);
baseMappingUrl = annotation.value();
}
// 获取当前类下的所有方法
Method[] methods = obj.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
// 判断当前方法是否含有@MyRequestMapping注解
if (method.isAnnotationPresent(MyRequestMapping.class)) {
// 获取MyRequestMapping的值
MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
//映射路径
String value = baseMappingUrl + myRequestMapping.value();
Handler handler = new Handler(Pattern.compile(value), obj, method);
// 获取方法的参数索引
Parameter[] parameters = method.getParameters();
for (int j = 0; j < parameters.length; j++) {
Parameter parameter = parameters[j];
if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
handler.getIndexMap().put(parameter.getType().getSimpleName(), j);
} else {
handler.getIndexMap().put(parameter.getName(), j);
}
}
handlerList.add(handler);
}
}
}
}
/**
* 依赖注入
*/
private void doAutoWired() {
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
Object obj = entry.getValue();
// 获取当前类下的所有属性
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
Field declaredField = declaredFields[i];
// 判断当前属性是否含有MyAutowired注解
if (declaredField.isAnnotationPresent(MyAutowired.class)) {
MyAutowired myAutowired = declaredField.getAnnotation(MyAutowired.class);
// 查看当前注解是否含有自定义beanID
String value = myAutowired.value();
if (StringUtils.isBlank(value)) {
// 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入) IDemoService
value = declaredField.getType().getName();
}
//暴力访问
declaredField.setAccessible(true);
try {
// 为属性赋值
declaredField.set(obj, iocMap.get(value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
/**
* 初始化带有注解的bean
* @throws Exception
*/
private void doInstance() {
if (classNames.size() == 0) return;
try {
for (int i = 0; i < classNames.size(); i++) {
String className = classNames.get(i);
if (StringUtils.isNotBlank(className)) {
Class<?> aClass = Class.forName(className);
// 将带有注解的实例存放在IOC容器中
if (aClass.isAnnotationPresent(MyController.class)) {
// 获取注解的值 作为bean的id controller不做处理 直接以类名首字母小写作为id
String simpleName = aClass.getSimpleName(); // DemoController
// 首字母小写
String id = doFirstLower(simpleName); // demoController
Object instance = aClass.newInstance();
iocMap.put(id, instance);
} else if (aClass.isAnnotationPresent(MyService.class)) {
// 获取注解的值 作为bean的id
MyService myService = aClass.getAnnotation(MyService.class);
String value = myService.value();
if (StringUtils.isBlank(value)) {
value = doFirstLower(aClass.getSimpleName());
}
Object instance = aClass.newInstance();
iocMap.put(value, instance);
// service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入
Class<?>[] interfaces = aClass.getInterfaces();
for (int j = 0; j < interfaces.length; j++) {
Class<?> anInterface = interfaces[j];
// 按照接口的全限定类名存储
iocMap.put(anInterface.getName(), instance);
}
} else {
continue;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 首字母小写
* @param simpleName
* @return
*/
private String doFirstLower(String simpleName) {
char[] chars = simpleName.toCharArray();
char aChar = chars[0];
if (aChar >= 'A' && aChar <= 'Z') {
chars[0] += 32;
}
return String.valueOf(chars);
}
/**
* 获取需要扫描的包下的所有class文件
* @param basePackage
*/
private void doScan(String basePackage) {
String path = Thread.currentThread().getContextClassLoader().getResource("").getPath() + basePackage.replaceAll("\\.", "/");
File file = new File(path);
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
File file1 = files[i];
// 如果是文件夹 递归
if (file1.isDirectory()) {
this.doScan(basePackage + "." + file1.getName());
} else if (file1.getName().endsWith("class")) {
//是.class文件 获取全限定类名
String s = basePackage + "." + file1.getName().replace(".class", "");
// 将所有class全限定类名存储 以便之后初始化
classNames.add(s);
}
}
}
private void doLoadConfig(String contextConfigLocation) throws IOException {
// 不处理classpath:
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
properties.load(resourceAsStream);
}
@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 {
//TODO do something
// 通过request中的URI获取handler
Handler handler = getHandler(req);
if (null == handler) {
resp.getWriter().write("404");
return;
}
// 获取当前方法
Method method = handler.getMethod();
// 获取方法的所有参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
// 定义一个参数数组 执行方法时传递的参数 数组个数应与参数类型的长度一致
Object[] params = new Object[parameterTypes.length];
// 获取请求中的所有参数
Map<String, String[]> parameterMap = req.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
// 判断handler中参数名称列表中是否包含当前参数
if (!handler.getIndexMap().containsKey(entry.getKey())) continue;
// 拼接value
String value = StringUtils.join(entry.getValue(), ",");
// 获取当前参数在形参中的索引
Integer index = handler.getIndexMap().get(entry.getKey());
// 将value放入参数数组的对应位置
params[index] = value;
}
// 将request放入参数数组的对应位置
Integer requestIndex = handler.getIndexMap().get(HttpServletRequest.class.getSimpleName());
if (requestIndex > -1) {
params[requestIndex] = req;
}
// 将response放入参数数组的对应位置
Integer responseIndex = handler.getIndexMap().get(HttpServletResponse.class.getSimpleName());
if (responseIndex > -1) {
params[responseIndex] = resp;
}
// 执行方法
try {
method.invoke(handler.getObject(), params);
} catch (Exception e) {
e.printStackTrace();
}
}
private Handler getHandler(HttpServletRequest req) {
if (handlerList.size() == 0) return null;
for (Handler handler : handlerList) {
if (handler.getUrl().matcher(req.getRequestURI()).matches()) {
return handler;
}
}
return null;
}
}
4. Handler
public class Handler {
private Pattern url;
// 执行Method.invoke(Object obj, args) 时需要
private Object object;
// 当前方法
private Method method;
// 参数索引
private Map<String, Integer> indexMap;
public Handler() {
}
public Handler(Pattern url, Object object, Method method) {
this.url = url;
this.object = object;
this.method = method;
this.indexMap = new HashMap<>();
}
public Pattern getUrl() {
return url;
}
public void setUrl(Pattern url) {
this.url = url;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Map<String, Integer> getIndexMap() {
return indexMap;
}
public void setIndexMap(Map<String, Integer> indexMap) {
this.indexMap = indexMap;
}
}
5. springmvc.properties
scanPackage=com.example
6. web.xml
<!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>mvc</servlet-name>
<servlet-class>com.example.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>springmvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
7. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springmvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<!-- scope必须设置为provided,否则不会识别自定义的MyDispatcherServlet -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件定义编译细节-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>8</source>
<target>8</target>
<encoding>utf-8</encoding>
<!--
告诉编译器,编译的时候记录下形参的真实名称
否则 method.getParameters()
获取到的Parameter getName()时获取到的不是参数的名称
而是 arg1 arg2
-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>