手写 SpringMVC 底层机制
-
前景提要:实现的是SpringMVC核心机制
-
对一些细枝末节的代码做了简化,比如字符串的处理...
-
完成哪些机制
- 机制一: 通过@RequestMapping ,可以标记一个方法,编写路径url,浏览器就能通过url完成调用
- 机制二: 进行依赖注入,使之不需要传统的new 一个对象,而是直接从IOC容器中获得
- 机制三:通过@RequestParam,如果浏览器传递的参数名和目标方法的形参不一致,可以通过value设置进行匹配
- 机制四:在目标方法完成后,跳转到相关页面 请求转发/重定向
- 机制五:在目标方法完成后,通过@Response注解,向浏览器发送JSON格式数据
手写添加配置
思路
- 需要配置pom.xml的依赖
- 需要写一个Servlet 作为前端控制器
- 需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟随Tomcat 自启动
- 需要配置spring容器配置文件
- 需要配置spring容器配置文件 扫描的路径 <component-scan ...>
实现
- 需要配置pom.xml的依赖
<!-- 配置原生Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
<!-- <scope> 表示引入的jar的作用范围
provided 表示该项目在打包 放到生产环境时,不需要带上 servlet-api.jar
因为tomcat本身有 servlet-api.jar,到时直接使用tomcat本身的 servlet-api.jar-->
</dependency>
<!-- 配置dom4j-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.1</version>
</dependency>
<!-- 配置常用的工具类-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
- 需要写一个Servlet 作为前端控制器
public class ZyDispatcherServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("ZyDispatcherServlet-doPost--");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("ZyDispatcherServlet-doGet--");
}
}
-
需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟随Tomcat 自启动
-
需要配置spring容器配置文件 扫描的路径 <component-scan ...>
<servlet>
<servlet-name>ZyDispatcherServlet</servlet-name>
<servlet-class>com.code_study.zyspringmvc.servlet.ZyDispatcherServlet</servlet-class>
<!--配置参数,指定要操作的spring配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:zyspringmvc.xml</param-value>
</init-param>
<!--跟随tomcat自启动-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ZyDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
- 需要配置spring容器配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!-- 要扫描的包-->
<component-scan base-package="com.code_study.controller,com.code_study.service"></component-scan>
</beans>
完成浏览器可以请求控制层
思路
- 创建@Controller和自己的Controller
- 编写工具类XMLParser,解析spring容器配置文件
- 开发自己的 Spring容器,得到扫描类的全路径列表
- 开发自己的 前端控制器,实例化对象到容器中
- 完成请求的URL和控制器方法的映射关系
- 完成前端控制器分发请求到对应控制器
- 自定义注解@Controller
- 创建Controller
- 需要写一个工具类XMLParser来解析在spring容器配置文件 扫描的路径 <component-scan ...> 的包 返回所有的路径
- 这个所有的路径 一个split(",")分隔,都进行扫描
- 需要写自己的 前端控制器
- 需要写自己的 Spring容器
- 在前端控制器中 需要添加方法 scanPackage() 扫描 XMLParser 解析出来的路径
- 在Spring容器中 需要添加一个属性 classFullPathList 来保存扫描出来的类的全路径
- 需要添加一个属性 ioc 来存放反射生成的bean对象 也就是过滤classFullPathList 中没有@Controller注解的一些路径 并实例化
- 需要添加类Handler 这个类要保存 一个url 对应的 一个控制器的方法的 映射 ,也就是说,根据这个url,可以找到对应控制器的对应方法
- 需要添加一个属性 HandlerList 用于 保存Handler 【url 和 控制器的映射】
- 需要添加三个方法 一个是initHandlerMapping(),完成 url 对应的 一个控制器的方法的 映射,即 将ioc 中bean 中的 方法进行反射,获取url,将 url,method,bean 封装成Handler 放入HandlerList 保存
- 添加第二个方法 getHandler(),需要将浏览器发送的request请求中的 uri拿出来,遍历HandlerList 进行配对,如果有 就返回对应的Handler
- 添加第三个方法 executeDispatch(),进行分发处理,需要 调用getHandler() 获取浏览器发送的request请求 对应的 Handler ,获取Handler 中的method 进行反射调用,method .invoke() 实现分发请求。
实现
- 自定义注解@Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
- 创建Controller
@Controller
public class MonsterController {
}
- 需要写一个工具类XMLParser来解析在spring容器配置文件 扫描的路径 <component-scan ...> 的包 返回所有的路径
public class XMLParser {
public static String getBasePackage(String xmlFile){
SAXReader saxReader = new SAXReader();
ClassLoader classLoader = XMLParser.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream(xmlFile);
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
Element element = rootElement.element("component-scan");
String basePackage = element.attribute("base-package").getText();
return basePackage;
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
}
- 这个所有的路径 一个split(",")分隔,都进行扫描
- 需要写自己的 前端控制器
- 需要写自己的 Spring容器
- 在前端控制器中 需要添加方法 scanPackage() 扫描 XMLParser 解析出来的路径
public void scanPackage(String pack) {
//获得包所在的工作路径 [绝对路径]
URL url =
this.getClass().getClassLoader().//获取类的加载器
//得到指定包对应的工作路径 [绝对路径]
getResource("/" + pack.replaceAll("\\.", "/"));
// System.out.println("url= "+url);
//根据得到的路径,对其进行扫描,把类的全路径 保存到 classFullPathList
String path = url.getFile();
//在io中 把目录也是为一个文件
File file = new File(path);
//遍历file 【遍历出文件和子目录】
for (File f : file.listFiles()) {
if (f.isDirectory()) {//如果是目录
//需要递归扫描 找子目录
scanPackage(pack + "." + f.getName());
} else {
//的确是个文件
//扫描到的文件可能是 .class 文件 也可能是其他文件
//就算是.class 文件 也需要判断是否需要注入容器 有无加 @Controller注解
//目前无法拿到注解 因为没法反射 所以先把文件的全路径都保存到 classFullPathList 之后在注入对象到容器时再处理
String classFullPath =
//类的全路径不需要.class 去掉.class
pack + "." + f.getName().replaceAll(".class", "");
//保存到 classFullPathList
classFullPathList.add(classFullPath);
}
}
}
- 在Spring容器中 需要添加一个属性 classFullPathList 来保存扫描出来的类的全路径
//保存扫描的包/子包类的全路径
private List<String> classFullPathList =
new ArrayList<>();
- 需要添加一个属性 ioc 来存放反射生成的bean对象 也就是过滤classFullPathList 中没有@Controller注解的一些路径 并实例化
//定义属性 ioc -> 存放反射生成的bean对象 比如Controller / Service /Dao
public ConcurrentHashMap<String, Object> ioc =
new ConcurrentHashMap<>();
- 编写方法,将扫描到的类,在满足情况下 反射到ioc容器
//编写方法,将扫描到的类,在满足情况下 反射到ioc容器
public void executeInstance() {
if (classFullPathList.size() == 0) {
//说明没有扫描到类
return;
}
//遍历classFullList
for (String classFullPath : classFullPathList) {
try {
Class<?> clazz = Class.forName(classFullPath);
if (clazz.isAnnotationPresent(Controller.class)) {//处理@Controller
String className = clazz.getSimpleName();
Object instance = clazz.newInstance();
String value = clazz.getAnnotation(Controller.class).value();
if (!"".equals(value)) {
className = value;
} else {
className = StringUtils.uncapitalize(className);
}
ioc.put(className, instance);
}
else if (clazz.isAnnotationPresent(Service.class)) {//处理@Service
String className = clazz.getSimpleName();//类名
Service serviceAnnotation = clazz.getAnnotation(Service.class);
String value = serviceAnnotation.value();
if (!"".equals(value)) {
className = value;
Object instance = clazz.newInstance();
ioc.put(className, instance);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
- 需要添加类Handler 这个类要保存 一个url 对应的 一个控制器的方法的 映射 ,也就是说,根据这个url,可以找到对应控制器的对应方法
ZyHandler {
private String url;
private Method method;
private Object controller;
public ZyHandler() {
}
public ZyHandler(String url, Method method, Object controller) {
this.url = url;
this.method = method;
this.controller = controller;
}
//需要提供getter和setter方法...
- 需要添加一个属性 HandlerList 用于 保存Handler 【url 和 控制器的映射】
//定义属性 HandlerList -> 保存ZyHandler 【url 和 控制器的映射】
private List<ZyHandler> HandlerList =
new ArrayList<>();
- 需要添加三个方法 一个是initHandlerMapping(),完成 url 对应的 一个控制器的方法的 映射,即 将ioc 中bean 中的 方法进行反射,获取url,将 url,method,bean 封装成Handler 放入HandlerList 保存
private void initHandlerMapping(){
//遍历 ioc
for (Map.Entry<String,Object> entry: zyWebApplicationContext.ioc.entrySet()) {
if (zyWebApplicationContext.ioc.isEmpty()){
return;
}
Object bean = entry.getValue();
Class<?> clazz = bean.getClass();
if (clazz.isAnnotationPresent(Controller.class)){
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.isAnnotationPresent(RequestMapping.class)){
String url = declaredMethod.getAnnotation(RequestMapping.class).value();
ZyHandler zyHandler = new ZyHandler(url, declaredMethod, bean);
HandlerList.add(zyHandler);
}
}
}
}
}
- 添加第二个方法 getHandler(),需要将浏览器发送的request请求中的 uri拿出来,遍历HandlerList 进行配对,如果有 就返回对应的Handler
//编写方法,通过request对象 返回ZyHandler对象 ,如果没有返回null
private ZyHandler getZyHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
//遍历HandlerList
for (ZyHandler zyHandler : HandlerList) {
if (requestURI.equals(zyHandler.getUrl())){
return zyHandler;
}
}
return null;
}
- 添加第三个方法 executeDispatch(),进行分发处理,需要 调用getHandler() 获取浏览器发送的request请求 对应的 Handler ,获取Handler 中的method 进行反射调用,method .invoke() 实现分发请求。
public void executeDispatch(HttpServletRequest request,HttpServletResponse response){
ZyHandler zyHandler = getZyHandler(request);
try {
if (null == zyHandler){
response.getWriter().write("<h1>404 NOT FOUND</h1>");
}
Method method = zyHandler.getMethod();
method.invoke(zyHandler.getController(),request,response);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
从web.xml文件中动态获取spring配置文件
思路
- 首先,我们自己写的前端控制器是一个Servlet,它有 servletConfig,可以servletConfig.getInitParameter("xxx") 来获取之前在web.xml配置的classpath:
- 工具类XMLParser 是在spring容器中解析的 ,web.xml配置的classpath: 是在前端控制器中获取的,因此需要spring容器提供有参构造器,在前端控制器添加参数 spring容器,将classpath 传到spring容器中进行解析。
实现
- 首先,我们自己写的前端控制器是一个Servlet,它有 servletConfig,可以servletConfig.getInitParameter("xxx") 来获取之前在web.xml配置的classpath:
- 工具类XMLParser 是在spring容器中解析的 ,web.xml配置的classpath: 是在前端控制器中获取的,因此需要spring容器提供有参构造器,在前端控制器添加参数 spring容器,将classpath 传到spring容器中进行解析。
@Override
public void init() throws ServletException {
String configLocation = getServletConfig().getInitParameter("contextConfigLocation");
System.out.println("ZyDispatcherServlet 初始化---");
zyWebApplicationContext = new ZyWebApplicationContext(configLocation);
zyWebApplicationContext.init();
initHandlerMapping();
System.out.println("HandlerList= "+HandlerList);
}
private String configLocation;
public ZyWebApplicationContext(String configLocation) {
this.configLocation = configLocation;
}
public void init(){
System.out.println("ZyWebApplicationContext 初始化---");
String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
String[] basePackages = basePackage.split(",");
if (basePackages.length >0) {
for (String pack : basePackages) {
scanPackage(pack);
}
}
}
@Service注解
思路
- @Service注解是写在类上的 即@Target(ElementType.TYPE)
- 这个注解标识的类就是一个Service,那么同样是在executeInstance()方法中判断是否有注解@Service,有的话就保存到ioc容器中
- 由于是Service,那么保存进ioc 的 k-v 中的 k 就有三种。第一种,就是默认值,用接口的类型的名字首字母小写;第二种,在@Service注解种设置了value属性,那么 k = value;第三章,需要用类名首字母小写也可以获取bean
实现
- @Service注解是写在类上的 即@Target(ElementType.TYPE)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
- 这个注解标识的类就是一个Service,那么同样是在executeInstance()方法中判断是否有注解@Service,有的话就保存到ioc容器中
- 由于是Service,那么保存进ioc 的 k-v 中的 k 就有三种。第一种,就是默认值,用接口的类型的名字首字母小写;第二种,在@Service注解种设置了value属性,那么 k = value;第三章,需要用类名首字母小写也可以获取bean
public void executeInstance() {
if (classFullPathList.size() == 0){
return;
}
//遍历 classFullPathList
for (String classFullPath : classFullPathList) {
try {
Class<?> clazz = Class.forName(classFullPath);
if (clazz.isAnnotationPresent(Controller.class)){
Controller controller = clazz.getAnnotation(Controller.class);
String value = controller.value();
String className = clazz.getSimpleName();
Object instance = clazz.newInstance();
if ("".equals(value)){
className = StringUtils.uncapitalize(className);
}else {
className = value;
}
ioc.put(className,instance);
} else if (clazz.isAnnotationPresent(Service.class)) {
Service serviceAnnotation = clazz.getAnnotation(Service.class);
String annoattionValue = serviceAnnotation.value();
Object instance = clazz.newInstance();
if ("".equals(annoattionValue)){
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
String simpleName = anInterface.getSimpleName();
simpleName = StringUtils.uncapitalize(simpleName);
ioc.put(simpleName,instance);
}
//可以通过类名首字母小写
String simpleName = clazz.getSimpleName();
simpleName = StringUtils.uncapitalize(simpleName);
ioc.put(simpleName,instance);
}else {
ioc.put(annoattionValue,instance);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Autowried 依赖注入
思路
- @Autowried注解是用在字段上,通过ioc容器自动装配,因此 @Target(ElementType.FIELD)
- 需要遍历ioc中所有的 bean 中的所有字段,来进行判断是否需要自动装配
- 通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值
- value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");
- 如果有值,去ioc容器进行查找该值对应的bean,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");如果有该bean,就用该字段的set()方法将本身的bean 和 查找ioc获取的bean进行装配
- 即 declaredField.set(bean, beanInIOC);
- 由于字段是私有属性private,需要暴力破解declaredField.setAccessible(true);
实现
- @Autowried注解是用在字段上,通过ioc容器自动装配,因此 @Target(ElementType.FIELD)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
-
需要遍历ioc中所有的 bean 中的所有字段,来进行判断是否需要自动装配
-
通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值
-
value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");
-
如果有值,去ioc容器进行查找该值对应的bean,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");如果有该bean,就用该字段的set()方法将本身的bean 和 查找ioc获取的bean进行装配
-
即 declaredField.set(bean, beanInIOC);
-
由于字段是私有属性private,需要暴力破解declaredField.setAccessible(true);
public void executeAutoWired(){
//遍历ioc
if (ioc.isEmpty()){
return;
}
//获取容器里的所有bean 以及 bean对应的字段
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Object bean = entry.getValue();
Class<?> clazz = bean.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
//通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值
if (declaredField.isAnnotationPresent(Autowired.class)){
Autowired annotation =
declaredField.getAnnotation(Autowired.class);
String beanName = annotation.value();
try {
//value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,
if ("".equals(beanName)){
beanName = declaredField.getType().getSimpleName();
beanName = StringUtils.uncapitalize(beanName);
}
Object iocBean = ioc.get(beanName);
//如果没有抛出空指针异常
if (null == iocBean){
throw new NullPointerException("ioc 没有该Bean");
}
declaredField.setAccessible(true);//暴力破解
declaredField.set(bean,iocBean);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
@RequestParam
思路
- @RequestParam 是写在PARAMETER上的 也就是说 @Target(ElementType.PARAMETER)
- 这个注解实现的功能 大方向是 分发请求 也就是说 写在 executeDispatch()方法中的
- 要实现 @RequestParam 需要将形参列表封装到一个数组中 , 因为反射 invoke 可以传入一个数组作为可变参数解析
- 那么就需要两个数组 一个数组是新创建的数组(大小应与,目标数组的形参个数一致) 将实参放入进去 ;另一个是 反射拿到的目标方法的形参的数组
- 这两个数组的内容需要一一对应,因为反射需要顺序一致
- 需要获取到request 中请求的参数Map 获取参数名和参数值
- 可以将功能具体化到 完成这个新数组的实参填写 【完成新数组之后放入invoke方法进行反射就行】
- 将步骤拆解成 1)完成HttpServletRequest 和 HttpServletResponse 的填写 ;2)完成带有@RequestParam注解的形参的填写;3)完成普通的方法参数没有@RequestParam注解的形参的填写
- 完成HttpServletRequest 和 HttpServletResponse 的填写:就需要先拿到目标方法的所有参数。根据类型的名称 和 形参列表进行匹配 填写
- 完成带有@RequestParam注解的形参的填写:需要添加方法,获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返回int 代表第几个参数,如果有@RequestParam注解,就会返回相应的索引,如果没有就会返回-1 进行普通方法参数的填写 处理
- 普通方法参数的 填写:需要添加方法,将目标方法的所有的形参的名称 反射保存到 List 返回,再通过遍历判断 请求的参数名 和 List中一致的 填写到 数组中
- 普通方法参数的 填写 需要插件,使用java8的特性 解决在默认情况下 parameter.getName() 获取的名字不是形参真正的名字,而是[arg0,arg1,arg2...]的问题
实现
- @RequestParam 是写在PARAMETER上的 也就是说 @Target(ElementType.PARAMETER)
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
- 这个注解实现的功能 大方向是 分发请求 也就是说 写在 executeDispatch()方法中的
- 要实现 @RequestParam 需要将形参列表封装到一个数组中 , 因为反射 invoke 可以传入一个数组作为可变参数解析
Method method = zyHandler.getMethod();
Class<?>[] parameterTypes = method.getParameterTypes();//形参数组
Object[] params = new Object[parameterTypes.length];
- 那么就需要两个数组 一个数组是新创建的数组(大小应与,目标数组的形参个数一致) 将实参放入进去 ;另一个是 反射拿到的目标方法的形参的数组
- 这两个数组的内容需要一一对应,因为反射需要顺序一致
- 需要获取到request 中请求的参数Map 获取参数名和参数值
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String name = entry.getKey();//参数 键
String value = entry.getValue()[0];//参数 值
-
可以将功能具体化到 完成这个新数组的实参填写 【完成新数组之后放入invoke方法进行反射就行】
-
将步骤拆解成 1)完成HttpServletRequest 和 HttpServletResponse 的填写 ;2)完成带有@RequestParam注解的形参的填写;3)完成普通的方法参数没有@RequestParam注解的形参的填写
-
完成HttpServletRequest 和 HttpServletResponse 的填写:就需要先拿到目标方法的所有参数。根据类型的名称 和 形参列表进行匹配 填写
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
params[i] = response;
}
}
- 完成带有@RequestParam注解的形参的填写:需要添加方法,获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返回int 代表第几个参数,如果有@RequestParam注解,就会返回相应的索引,如果没有就会返回-1 进行普通方法参数的填写 处理
//获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返
private int getRequestParamterIndex(Method method,String name){
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (parameter.isAnnotationPresent(RequestParam.class)) {
String value = parameter.getAnnotation(RequestParam.class).value();
if (name.equals(value)) {
return i;//返回int 代表第几个参数
}
}
}
return -1;
}
int requestParamterIndex = getRequestParamterIndex(zyHandler.getMethod(), name);
if (requestParamterIndex != -1) {
params[requestParamterIndex] = value;
}
- 普通方法参数的 填写:需要添加方法,将目标方法的所有的形参的名称 反射保存到 List 返回,再通过遍历判断 请求的参数名 和 List中一致的 填写到 数组中
//将目标方法的所有的形参的名称 反射保存到 List 返回
private List<String> getParameterNames(Method method){
List<String> parameterList = new ArrayList<>();
//获取到所有的参数名称
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
//在默认情况下 parameter.getName() 获取的名字不是形参真正的名字
//而是[arg0,arg1,arg2...]
//需要插件,使用java8的特性 解决
String name = parameter.getName();
parameterList.add(name);
}
System.out.println("目标方法的形参列表=" + parameterList);
return parameterList;
}
else {
//没找到@RequestParam 对应参数--使用默认机制
//1. 得到目标方法的所有形参名
//2. 对得到目标方法的所有形参名进行遍历,
//如果匹配就把当前请求的参数值放入params
List<String> parameterNames = getParameterNames(zyHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
if (name.equals(parameterNames.get(i))) {
params[i] = value;
break;
}
}
}
- 普通方法参数的 填写 需要插件,使用java8的特性 解决在默认情况下 parameter.getName() 获取的名字不是形参真正的名字,而是[arg0,arg1,arg2...]的问题
<!--可以进行json操作-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.0</version>
</dependency>
- 完整代码
public void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
ZyHandler zyHandler = getZyHandler(request);
try {
if (null == zyHandler) {
response.getWriter().write("<h1>404 NOT FOUND!</h1>");
} else {
Method method = zyHandler.getMethod();
Class<?>[] parameterTypes = method.getParameterTypes();//形参数组
Object[] params = new Object[parameterTypes.length];
//遍历 parameterTypes
//获取 HttpServletRequest , HttpServletResponse 在形参数组中的位置
//将request 和 response 保存到 params相应的位置上
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
params[i] = response;
}
}
//获取request中的
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String name = entry.getKey();//参数 键
String value = entry.getValue()[0];//参数 值
//获取形参数组中 带有@RequestParam 的形参 的位置
//将带有@RequestParam 保存到 params相应的位置上
int requestParamterIndex = getRequestParamterIndex(zyHandler.getMethod(), name);
if (requestParamterIndex != -1) {
params[requestParamterIndex] = value;
} else {
//没找到@RequestParam 对应参数--使用默认机制
//1. 得到目标方法的所有形参名
//2. 对得到目标方法的所有形参名进行遍历,
//如果匹配就把当前请求的参数值放入params
List<String> parameterNames = getParameterNames(zyHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
if (name.equals(parameterNames.get(i))) {
params[i] = value;
break;
}
}
}
}
视图解析
思路
- 首先 视图解析 它是在分发请求后 在目标方法中进行视图的跳转 forward 或者 redirect,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
- 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
- 当返回的是String类型后 我们就可以根据 splic(":")进行分隔
- splic(":")[0] 就是进行跳转的方式 forward 或者 redirect
- splic(":")[1] 就是进行跳转的页面
- 如果没有":" ,就说明是默认情况,forward 处理即可
实现
-
首先 视图解析 它是在分发请求后 在目标方法中进行视图的跳转 forward 或者 redirect,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
-
在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
Object result = zyHandler.getMethod().invoke(zyHandler.getController(), params);
-
当返回的是String类型后 我们就可以根据 splic(":")进行分隔
-
splic(":")[0] 就是进行跳转的方式 forward 或者 redirect
-
splic(":")[1] 就是进行跳转的页面文章来源:https://www.toymoban.com/news/detail-856739.html
-
如果没有":" ,就说明是默认情况,forward 处理即可文章来源地址https://www.toymoban.com/news/detail-856739.html
if (result instanceof String){
String viewName = (String) result;
if (viewName.contains(":")) {
String viewType = viewName.split(":")[0];
String viewPage = viewName.split(":")[1];
if ("forward".equals(viewType)){
request.getRequestDispatcher(viewPage).forward(request,response);
}else if (("redirect".equals(viewType))){
response.sendRedirect(viewPage);
}
}else {
request.getRequestDispatcher(viewName).forward(request,response);
}
}
@ResponseBody 返回JSON数据
实现
- 首先@ResponseBody 是写在方法上的 因此 @Target(ElementType.METHOD)
- 没有默认值 @ResponseBody仅仅作为一个标识
- @ResponseBody它是在分发请求后 在目标方法中标识该方法返回JSON格式的数据给浏览器,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
- 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
- 当返回的是一个集合类型,并且判断该方法有注解@ResponseBody ,就说明是需要向浏览器返回JSON格式数据
- 用jackson 包下的 objectWriter对象的 objectMapper.writeValueAsString()方法 ,可以很轻松的将集合转化为JSON进行返回
- 不要忘了设置response格式,防止乱码,response.setContentType("text/html;charset=utf-8");
- 直接用response.getWriter.writer()将转化后的结果返回给浏览器即可
思路
- 首先@ResponseBody 是写在方法上的 因此 @Target(ElementType.METHOD)
- 没有默认值 @ResponseBody仅仅作为一个标识
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
- @ResponseBody它是在分发请求后 在目标方法中标识该方法返回JSON格式的数据给浏览器,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
- 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
- 当返回的是一个集合类型,并且判断该方法有注解@ResponseBody ,就说明是需要向浏览器返回JSON格式数据
- 用jackson 包下的 objectWriter对象的 objectMapper.writeValueAsString()方法 ,可以很轻松的将集合转化为JSON进行返回
else if (result instanceof ArrayList) {
if (zyHandler.getMethod().isAnnotationPresent(ResponseBody.class)) {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result);
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
writer.close();
}
}
- 不要忘了设置response格式,防止乱码,response.setContentType("text/html;charset=utf-8");
- 直接用response.getWriter.writer()将转化后的结果返回给浏览器即可
到了这里,关于手写SpringMVC底层机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!