闭关修炼180天 -- 手写SpringMVC框架(迷你版)
SpringMvc知识须知
MVC设计模式
- Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业 务。
- View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据 模型数据创建的。
- Controller(控制器): 是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理程序逻辑的。
springMvc请求执行流程
- ⽤户发送请求⾄前端控制器DispatcherServlet
- DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
- 处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截 器(如果 有则⽣成)⼀并返回DispatcherServlet
- DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
- 处理器适配器执⾏Handler
- Handler执⾏完成给处理器适配器返回ModelAndView
- 处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个 底层对 象,包括 Model 和 View
- 前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
- 视图解析器向前端控制器返回View
- 前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
- 前端控制器向⽤户响应结果
SpringMVC九大核心组件
- 1.HandlerMapping(处理器映射器):找到请求响应的处理器Handler和Interceptor
- 2.HandlerAdapter(处理器适配器):要让固定的Servlet处理方法调用Handler来处理
- 3.HandlerExceptionResolver(异常处理器),用于处理Handler产生的异常情况
- 4.ViewResolver(视图解析器),将Sting类型的视图名解析为View类型的视图
- 5.RequestToViewNameTranslator,在请求域中获取ViewName,当Handler处理完后,没有设置View,那便从这个组件中从请求中获取ViewName
- 6.LocaleResolver,区域化国际化组件
- 7.ThemeResolver 主题解析器
- 8.MultipartResolver多文件上传解析器
- 9.FlashMapManager,用于重定向时的参数传递
自定义SpringMVC框架
大致流程
- 引进javax.servlet坐标,创建servlet包,创建HttpServlet的实现类MyServlet
- 创建annotation包,创建四个注解:@MyController,@MyRequestMapping,@MyService,@MyAutowired
- 重写HttpServlet的几个方法init(),doPost(),doGet()
- 在init()方法中加载解析springMvc.xml文件
- 在init()方法中完成注解的功能增强。
- 在init()方法中初始化ioc容器
- 在init()方法中完成依赖的注入
- 构造一个处理器映射器HandlerMapping,将我们处理好的url和Method建立映射关系
- 处理权限关系
项目结构
代码实现
1.pom文件主要内容
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</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> <!--告诉编译器,编译的时候记录下形参的真实名称--> <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>
2.web.xml 以及 springmvc.properties
<!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>MyServlet</servlet-name> <servlet-class>com.zae.frame.servlet.MyServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>springmvc.properties</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
scanPackage=com.zae
3.五个注解的定义
import java.lang.annotation.*; @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAutowired { String value() default ""; }
import java.lang.annotation.*; @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyController { String value() default ""; }
import java.lang.annotation.*; @Documented @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value() default ""; }
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyService { String value() default ""; }
/** * 1.当未添加该注解时,表明该方法不加入权限控制,所有请求均可访问 * 2.当该注解放置在某个类上,则表明该类下的所有方法都可以被该注解指定的用户访问 * 3.当该注解放置在某个方法时,则以该方法上的用户指定为准 */ @Target({ElementType.TYPE,ElementType.METHOD}) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Security { //配置用户名称,指定的用户可以访问 String [] value() default {"/"}; }
4.handler实体的定义
import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; /** * 封装映射关系 */ public class Handler { //controller private Object controller; //方法 private Method method; //url的正则表达式 private Pattern pattern; //存储参数字段以及其的入参位置 private Map<String,Integer> handlerMapping; //存储有权限访问的用户 private String [] securityUser; public Handler(Object controller, Method method, Pattern pattern) { this.controller = controller; this.method = method; this.pattern = pattern; this.handlerMapping = new HashMap<String, Integer>(); } public String[] getSecurityUser() { return securityUser; } public void setSecurityUser(String[] securityUser) { this.securityUser = securityUser; } public Object getController() { return controller; } public void setController(Object controller) { 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 Map<String, Integer> getHandlerMapping() { return handlerMapping; } public void setHandlerMapping(Map<String, Integer> handlerMapping) { this.handlerMapping = handlerMapping; } }
5.MyServlet核心类的实现
import com.zae.frame.annotation.*; import com.zae.frame.pojo.Handler; import org.apache.commons.lang3.StringUtils; import javax.servlet.ServletConfig; 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.Matcher; import java.util.regex.Pattern; public class MyServlet extends HttpServlet { //配置文件信息:springmvc.xml Properties properties = new Properties(); //存放扫描包下的全限类名 List<String> classList = new ArrayList<String>(); //ioc容器,存放实例对象 Map<String,Object> beanMap = new HashMap<String,Object>(); //存放Handler的集合 List<Handler> handlerList = new ArrayList<Handler>(); @Override public void init(ServletConfig config){ //1.加载配置文件:springmvc.xml String path = config.getInitParameter("contextConfigLocation"); doLoanConfig(path); //2.扫描包下所有的类 doScan(properties.getProperty("scanPackage")); //3.初始化bean对象,基于注解,加入ioc容器 doInstance(); //4.完成依赖的注入@MyAutowired doAutowired(); //5.构建好一个HandlerMapping,完成url和method之间的映射关系 initHandleMapping(); //6.处理用户权限问题 doSecurity(); System.out.println("迷你版SpringMvc初始化完成...."); } /** * 完成Controller层的用户访问权限问题 */ private void doSecurity() { if (handlerList.size() == 0){return;} for(Handler handler:handlerList){ Class<?> aClass = handler.getController().getClass(); String [] securityUserArr = null; //1.当controller类上有@Security注解时,则改类中所有的方法都加入权限控制,以类上的@Security里面的value为准 if(aClass.isAnnotationPresent(Security.class)){ Security annotation = aClass.getAnnotation(Security.class); securityUserArr = annotation.value(); } //2.当方法上存在@Security注解时,则以方法的@Security中的value为准,覆盖类上声明的那个权限注解 Method method = handler.getMethod(); if(method.isAnnotationPresent(Security.class)){ Security annotation = method.getAnnotation(Security.class); securityUserArr = annotation.value(); } //3.如果无论controller类还是其中的方法都没有加Security注解,此时securityUserArr==null,那么可以任意访问,不会拦截, //4.如果controller层加了Security注解,但是没有重新定义value值或者value值声明为{"/"},那么此时securityUserArr=={"/"},会拦截所有用户的请求 handler.setSecurityUser(securityUserArr); } } /** * 完成url与方法之间的映射 */ private void initHandleMapping() { if(beanMap.size() == 0){return;} for(Map.Entry<String,Object> map:beanMap.entrySet()){ Class<?> aClass = map.getValue().getClass(); String baseUrl = ""; //当controller类上有MyRequestMapping注解时,基础url为value值 if(aClass.isAnnotationPresent(MyRequestMapping.class)){ MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class); baseUrl = annotation.value(); } //获取Controller的所有方法 Method[] methods = aClass.getMethods(); for (Method method : methods) { //当方法上有MyRequestMapping注解时,获取其value值 if(method.isAnnotationPresent(MyRequestMapping.class)){ MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class); //请求url String url = baseUrl+annotation.value(); Pattern compile = Pattern.compile(url); Handler handler = new Handler(map.getValue(),method,compile); //计算方法的参数位置信息 Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //当参数为HttpServletRequest和HttpServletResponse时,key存储的是参数的类型名 if(parameters[i].getType() == HttpServletRequest.class || parameters[i].getType() == HttpServletResponse.class){ handler.getHandlerMapping().put(parameters[i].getType().getSimpleName(),i); }else{ //当其他情况的时候,key存储的是参数名称 handler.getHandlerMapping().put(parameters[i].getName(),i); } } //将handler存储起来 handlerList.add(handler); } } } } /** * 依赖注入 */ private void doAutowired() { if(beanMap.size()==0){return;} for(Map.Entry<String,Object> mapEntry:beanMap.entrySet()){ String key = mapEntry.getKey(); Object value = mapEntry.getValue(); Class<?> aClass = value.getClass(); //获取该类下所有的属性 Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { //判断属性上是否存在MyAutowired注解 if(declaredField.isAnnotationPresent(MyAutowired.class)){ MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class); //获取注解里面的value值 String name = annotation.value(); //设置暴力访问 declaredField.setAccessible(true); if("".equals(name.trim())){ //当注解的value值为空时,获取属性类型的全限类名,使用类型注入依赖 name = declaredField.getType().getName(); } try { //给该属性赋值 declaredField.set(value,beanMap.get(name)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } //依赖赋值结束后,重新加入map集合中 beanMap.put(key,value); } } /** * 基于注解完成ioc的初始化 */ private void doInstance() { if(classList.size()==0){return;} for (String className : classList) { try { //根据全限类名获取类对象 Class<?> aClass = Class.forName(className); if(aClass.isAnnotationPresent(MyService.class)){ MyService annotation = aClass.getAnnotation(MyService.class); String value = annotation.value(); Object beanObj = aClass.newInstance(); if("".equals(value)){ //当service注解为空时,使用首字母小写的类名 value = firstToLower(aClass.getSimpleName()); beanMap.put(value,beanObj); }else{ //当service的value不为空时,使用注解里面的value值 beanMap.put(value,beanObj); } //面向接口开发,以接口全限类名作为id放入容器(针对service层存在接口) Class<?>[] interfaces = aClass.getInterfaces(); if(interfaces!=null){ for (Class<?> anInterface : interfaces) { beanMap.put(anInterface.getName(),aClass.newInstance()); } } }else if(aClass.isAnnotationPresent(MyController.class)){ //MyController注解的类使用首字母小写的类名 Object beanObj = aClass.newInstance(); beanMap.put(firstToLower(aClass.getSimpleName()),beanObj); }else{ continue; } } catch (Exception e) { e.printStackTrace(); } } } private String firstToLower(String source){ if(source==null){return null;} char[] chars = source.toCharArray(); if(chars[0]>='A' && chars[0]<='Z'){ chars[0] += 32; } return String.valueOf(chars); } /** * 扫描包下所有的类 * @param scanPackage */ private void doScan(String scanPackage) { String scanPackagePath =Thread.currentThread().getContextClassLoader().getResource("").getPath()+scanPackage.replaceAll("\\.","/"); File fileScan = new File(scanPackagePath); File[] files = fileScan.listFiles(); for (File file : files) { if(file.isDirectory()){ //如果是文件夹,就继续递归 doScan(scanPackage+"."+file.getName()); }else if(file.getName().endsWith(".class")){ String className = scanPackage+"."+file.getName().replaceAll(".class",""); classList.add(className); } } } /** * 加载springmvc.properties配置文件 * @param path */ private void doLoanConfig(String path) { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(path); try { properties.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { //设置中文编码格式 resp.setContentType("text/html;charset=UTF-8"); doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { Handler handler = getHandler(req); if(handler == null){ resp.getWriter().write("404 No fount"); return; } //检查用户权限问题 if(!checkSecurity(handler,req,resp)){return;} //参数绑定工作 Class<?>[] parameterTypes = handler.getMethod().getParameterTypes(); //根据上述长度创建一个新的数组,参数传递时反射调用 Object[] objParam = new Object[parameterTypes.length]; //为了向参数数组中塞值,保证形参和方法的参数顺序一致 Map<String, String[]> parameterMap = req.getParameterMap(); //拿到方法的入参顺序 Map<String, Integer> handlerMapping = handler.getHandlerMapping(); for(Map.Entry<String,String[]> map:parameterMap.entrySet()){ String join = StringUtils.join(map.getValue(), ","); String key = map.getKey(); //如果字段名字匹配上了,则进行赋值操作 if(parameterMap.containsKey(key)){ objParam[handlerMapping.get(key)] = join; } } //处理HttpServletRequest和HttpServletResponse int reqIndex = handlerMapping.get(HttpServletRequest.class.getSimpleName()); int respIndex = handlerMapping.get(HttpServletResponse.class.getSimpleName()); objParam[reqIndex] = req; objParam[respIndex] = resp; Method method =handler.getMethod(); Object controllerObj = handler.getController(); try { //进行方法调用 method.invoke(controllerObj,objParam); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } /** * 检查用户权限问题 * @param req * @return */ private Boolean checkSecurity(Handler handler,HttpServletRequest req,HttpServletResponse resp) throws IOException { //当securityUser == null时,没有加权限控制,任意请求都能访问 if(handler.getSecurityUser() == null){ return true; } //当securityUser == {/},任意请求都不能访问 if("/".equals(handler.getSecurityUser()[0])){ resp.getWriter().write("您无权访问!请联系管理员"); return false; } //前端的参数,访问的用户 String username = req.getParameter("username"); //当securityUser存在值时,则进行校验 List<String> userList = Arrays.asList(handler.getSecurityUser()); if(userList.contains(username)){ return true; }else{ resp.getWriter().write("您无权访问!请联系管理员"); return false; } } /** * 根据请求获取Handler对象 * @param req * @return */ private Handler getHandler(HttpServletRequest req){ if(handlerList.isEmpty()){return null;} String url = req.getRequestURI(); for (Handler handler : handlerList) { //判断url和正则是否匹配 Matcher matcher = handler.getPattern().matcher(url); if(!matcher.matches()){continue;} return handler; } return null; } }
6.使用端Service以及Controller(测试使用,不再关联数据库了)
public interface TestService { String start(String username); }
import com.zae.demo.service.TestService; import com.zae.frame.annotation.MyService; @MyService public class TestServiceImpl implements TestService { @Override public String start(String username) { System.out.println("访问的用户为:"+username); return username; } }
import com.zae.demo.service.TestService; import com.zae.frame.annotation.MyAutowired; import com.zae.frame.annotation.MyController; import com.zae.frame.annotation.MyRequestMapping; import com.zae.frame.annotation.Security; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Security({"wangsulong","xuezhiqian"}) @MyController @MyRequestMapping("/demoSecurity") public class TestSecurityController { @MyAutowired private TestService testService; /** * 方法中有@Security,则覆盖类定义的权限注解 * @param request * @param response * @param username * @return */ @Security({"xusong"}) @MyRequestMapping("/startSecurityOne") public String startSecurityOne(HttpServletRequest request, HttpServletResponse response,String username){ return testService.start(username); } /** * 方法中没有@Security,则使用类里面的权限注解定义的 * @param request * @param response * @param username * @return */ @MyRequestMapping("/startSecurityTwo") public String startSecurityTwo(HttpServletRequest request, HttpServletResponse response,String username){ return testService.start(username); } /** * @Security中的value没有设定值,则拦截所欲 * @param request * @param response * @param username * @return */ @Security @MyRequestMapping("/startSecurityThree") public String startSecurityThree(HttpServletRequest request, HttpServletResponse response,String username){ return testService.start(username); } }
自定义SpringMvc框架至此结束
文章知识点输出来源:拉勾教育Java高薪训练营
2021第一天,我是帝莘,期待和你的技术交流以及思想碰撞