手写MVC
1.SpringMvc执行大致原理
2.手写Mvc之注解开发
2.0.配置文件
- 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>org.example</groupId>
<artifactId>mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mvc 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>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--commoms-lang-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件,防止jdk在编译时将参数识别为args1,而不使用自己原本的参数名称-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<encoding>utf-8</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
<version>2.2</version>
</plugin>
</plugins>
</build>
</project>
- 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>MvcDispatcherServlet</servlet-name>
<servlet-class>com.edu.mvcframework.servlet.MvcDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>mvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MvcDispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
- mvc.properties
scanPackage=com.demo
2.1.自定义注解类
- MvcController
package com.edu.mvcframework.annotation;
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)
public @interface MvcController {
String value() default "";
}
- MvcService
package com.edu.mvcframework.annotation;
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)
public @interface MvcService {
String value() default "";
}
- MvcRequestMapping
package com.edu.mvcframework.annotation;
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)
public @interface MvcRequestMapping {
String value() default "";
}
- MvcAutowire
package com.edu.mvcframework.annotation;
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)
public @interface MvcAutowire {
String value() default "";
}
- Security
package com.edu.mvcframework.annotation;
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)
public @interface Security {
String[] value();
}
2.2.自定义前端控制器
- MvcDispatcherServlet
package com.edu.mvcframework.servlet;
import com.edu.mvcframework.annotation.*;
import com.edu.mvcframework.pojo.Handle;
import org.apache.commons.lang3.StringUtils;
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.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;
public class MvcDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
// 定义一个容器,存储扫描到的所有类的全路径名称
private Set<String> classNames = new HashSet<>();
// ioc容器
private Map<String,Object> iocMap = new HashMap<>();
// 存储url和method的映射关系容器
private Map<String, Handle> handlerMap = new HashMap<>();
// 标识已处理过的类
private Set<String> handledClasses = new HashSet<>();
@Override
public void init(ServletConfig config) throws ServletException {
String mvcPropFile = config.getInitParameter("contextConfigLocation");
try {
// 1.加载自定义mvc框架配置文件
doLoadProperties(mvcPropFile);
// 2.扫描包,获取所有类
doScan(properties.getProperty("scanPackage"));
// 3.实例化对象,将含有自定义注解的类存储到ioc容器当中
doInstance();
// 4.处理类之间的依赖关系
doAutowire();
// 5.处理url和method之间的映射关系,进行存储
doInitHandleMapping();
// 等待请求到来
}catch (Exception e){
e.printStackTrace();
}
}
private void doInitHandleMapping() throws ClassNotFoundException {
if(iocMap.size() == 0){return;}
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
Class<?> aClass = entry.getValue().getClass();
// 已经处理过映射的不在处理
if (handledClasses.contains(aClass.getName())){continue;}
// 非controller类,不进行处理
if (!aClass.isAnnotationPresent(MvcController.class)){continue;}
if (aClass.isAnnotationPresent(MvcRequestMapping.class)){
MvcRequestMapping baseUrlAnno = aClass.getAnnotation(MvcRequestMapping.class);
String baseUrl = baseUrlAnno.value(); // /demo
// 获取所有的方法,判断是否含有requestMapping注解
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
// 方法没有MvcRequestMapping标识,不做处理
if (!declaredMethod.isAnnotationPresent(MvcRequestMapping.class)) {
continue;
}
MvcRequestMapping methodUrlAnno = declaredMethod.getAnnotation(MvcRequestMapping.class);
String methodUrl = methodUrlAnno.value(); // /getName
String url = baseUrl + methodUrl; // /demo/getName
// 把method所有信息及url封装为⼀个Handler
Handle handle = new Handle(declaredMethod,Pattern.compile(url),entry.getValue());
// 参数顺序设置
Parameter[] parameters = declaredMethod.getParameters();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].getType() == HttpServletRequest.class
|| parameters[i].getType() == HttpServletResponse.class ){
// 如果是request和response对象,
// 那么参数名称写HttpServletRequest和HttpServletResponse
handle.getParametersIndex().put(parameters[i].getType().getSimpleName(),i);
}else {
handle.getParametersIndex().put(parameters[i].getName(),i);
}
}
handlerMap.put(url,handle);
}
handledClasses.add(aClass.getName());
}
}
handledClasses.clear();
}
private void doAutowire() throws IllegalAccessException {
if(iocMap.size() == 0){return;}
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
Class<?> aClass = entry.getValue().getClass();
// ioc容器中存在根据接口,以及本身类名存储的实例化对象。即一个实例化对象的key可能有多个
// 已经处理过不在处理
if (handledClasses.contains(aClass.getName())){
continue;
}
// 获取所有字段,判断是否有autowire注解,有则进行依赖
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (!declaredField.isAnnotationPresent(MvcAutowire.class)) {
continue;
}
MvcAutowire autowireAnno = declaredField.getAnnotation(MvcAutowire.class);
String beanName = autowireAnno.value();
if("".equals(beanName.trim())){
// 没有配置bean的Id,则按照接口注入
beanName = declaredField.getType().getName();
}
declaredField.setAccessible(true);
declaredField.set(entry.getValue(),iocMap.get(beanName));
}
// 处理完标记下已经处理过
handledClasses.add(aClass.getName());
}
handledClasses.clear();
}
private void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if(classNames.size() == 0){return;}
for (String className : classNames) {
Class<?> aClass = Class.forName(className);
// 如果是Controller注解,对名称不做过多处理
if (aClass.isAnnotationPresent(MvcController.class)){
iocMap.put(lowerFirst(aClass.getSimpleName()),aClass.newInstance());
}else if(aClass.isAnnotationPresent(MvcService.class)){
// 如果是service注解,需要取值存放,没有需要默认类名存放
MvcService serviceAnno = aClass.getAnnotation(MvcService.class);
String beanName = serviceAnno.value();
Object newInstance = aClass.newInstance();
if("".equals(beanName.trim())){
beanName = lowerFirst(aClass.getSimpleName());
}
iocMap.put(beanName,newInstance);
//如果实现了接口,需要按接口名称再存放一份,以接⼝的全限定类名作为id放⼊
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
iocMap.put(anInterface.getName(),newInstance);
}
}else {
continue;
}
}
}
private String lowerFirst(String str){
char[] chars = str.toCharArray();
if('A' <= chars[0] && chars[0] <= 'Z'){
chars[0] += 32;
}
return String.valueOf(chars);
}
private void doScan(String basePackage) {
// 需要将.替换成/
String pack = this.getClass().getClassLoader().getResource("").getPath() +
basePackage.replaceAll("\\.", "/"); // com.demo com/demo
File packs = new File(pack);
File[] files = packs.listFiles();
for (File file : files) {
if(file.isDirectory()){
doScan(basePackage +"."+file.getName());
}else if (file.getName().endsWith(".class")){
String className = basePackage +"."+ file.getName().replaceAll(".class","");
classNames.add(className);
}else {
continue;
}
}
}
private void doLoadProperties(String mvcPropFile) throws IOException {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mvcPropFile);
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 {
// 处理请求
String requestURI = req.getRequestURI(); // /demo/getName
// 根据uri获取到能够处理当前请求的hanlder Handle handle = getHandle(requestURI);
Handle handle = handlerMap.get(requestURI);
if (handle == null){
resp.getWriter().write("404 not found");
return;
}
// 权限校验
String userName = req.getParameter("username");
if(!doSecurity(handle,userName)){
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write(userName + "无权限访问...");
return;
}
Method method = handle.getMethod();
// 参数绑定
Map<String, Integer> parametersIndex = handle.getParametersIndex();
Object[] params = new Object[parametersIndex.size()];
// 参数为HttpServletRequest,HttpServletResponse
int requestIndex =parametersIndex.get(HttpServletRequest.class.getSimpleName());
params[requestIndex] = req;
int responseIndex =parametersIndex.get(HttpServletResponse.class.getSimpleName());
params[responseIndex] = resp;
// 遍历request中所有参数 (填充除了request,response之外的参数)
Map<String, String[]> parameterMap = req.getParameterMap();
for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
// name=1&name=2这种 name[1,2]
String paramVal = StringUtils.join(stringEntry.getValue(), ","); // 如同 1,2
if (parametersIndex.keySet().contains(stringEntry.getKey())){
Integer index = parametersIndex.get(stringEntry.getKey());
// 把前台传递过来的参数值填充到对应的位置去
params[index] = paramVal;
}
}
try {
method.invoke(handle.getController(),params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private boolean doSecurity(Handle handle, String userName) {
boolean controllerPrivilege = true;
boolean methodPrivilege = true;
// 获取controller上的@Sercurity注解
Class<?> controllerClass = handle.getController().getClass();
if (controllerClass.isAnnotationPresent(Security.class)){
Security securityAnno = controllerClass.getAnnotation(Security.class);
String[] securityAnnoArray = securityAnno.value();
if (Objects.nonNull(securityAnnoArray) && securityAnnoArray.length != 0){
if (!Arrays.asList(securityAnnoArray).contains(userName)){
controllerPrivilege = false;
}
}
}
// 获取方法上的注解
if (handle.getMethod().isAnnotationPresent(Security.class)){
Security securityAnno = handle.getMethod().getAnnotation(Security.class);
String[] securityAnnoArray = securityAnno.value();
if (Objects.nonNull(securityAnnoArray) && securityAnnoArray.length != 0){
if (!Arrays.asList(securityAnnoArray).contains(userName)){
methodPrivilege = false;
}
}
}
// 只要controller或者method上有@sercurity注解,就获取权限名单,如果用户在名单中,就放行
return controllerPrivilege && methodPrivilege;
}
private Handle getHandle(String uri){
for (Map.Entry<String, Handle> stringHandleEntry : handlerMap.entrySet()) {
Handle handle = stringHandleEntry.getValue();
if (! handle.getPattern().matcher(uri).matches()){
continue;
}else {
return handle;
}
}
return null;
}
}
- 前端控制器的init方法会在第一次访问servlet时进行调用.在init逻辑中还增添了简单的权限校验.
2.3.pojo类Handle类
package com.edu.mvcframework.pojo;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class Handle {
private Method method;
private Pattern pattern;
private Object controller;
private Map<String,Integer> parametersIndex = new HashMap<>();// 参数对应位置
public Handle(Method method, Pattern pattern, Object controller) {
this.method = method;
this.pattern = pattern;
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Map<String, Integer> getParametersIndex() {
return parametersIndex;
}
public void setParametersIndex(Map<String, Integer> parametersIndex) {
this.parametersIndex = parametersIndex;
}
}
3.测试自定义框架
3.1.controller层
- DemoController
package com.demo.controller;
import com.demo.service.IDemoService;
import com.edu.mvcframework.annotation.MvcAutowire;
import com.edu.mvcframework.annotation.MvcController;
import com.edu.mvcframework.annotation.MvcRequestMapping;
import com.edu.mvcframework.annotation.Security;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@MvcController
@MvcRequestMapping("/demo")
@Security({"zhangsan","lisi","wangwu"})
public class DemoController {
@MvcAutowire
private IDemoService demoService;
@MvcRequestMapping("/security01")
public void security01(HttpServletRequest request, HttpServletResponse response,String username) throws IOException {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(username +"访问security01成功....");
}
@MvcRequestMapping("/security02")
@Security({"zhangsan"})
public void security02(HttpServletRequest request, HttpServletResponse response,String username) throws IOException {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(username + "访问security02成功....");
}
@MvcRequestMapping("/getName")
public void getName(HttpServletRequest request, HttpServletResponse response,String name){
demoService.get(name);
}
}
3.2.service层
- IDemoService
package com.demo.service;
public interface IDemoService {
public void get(String name);
}
- DemoServiceImpl
package com.demo.service.impl;
import com.demo.service.IDemoService;
import com.edu.mvcframework.annotation.MvcService;
@MvcService
public class DemoServiceImpl implements IDemoService {
@Override
public void get(String name) {
System.out.println("进入到了service实现类...name是:{"+name+"}");
}
}
3.3.启动测试
debug启动tomcat插件
访问:http://localhost:8080/demo/getName?name=zhangsan&name=lis
文章内容输出来源:拉勾教育Java高薪训练营