整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Spring面试题

Spring面试题

pring Bean实例化流程

在 Spring 框架中,Bean 的实例化流程通常包括以下步骤:

  1. 加载配置文件:Spring 容器会加载配置文件,包括 XML 配置文件、注解配置等,用于定义 Bean 的信息。
  2. 解析配置文件:Spring 容器会解析配置文件,识别出配置中定义的 Bean,以及它们之间的依赖关系。
  3. 实例化 Bean:Spring 容器根据配置文件中的信息,实例化每个 Bean。这通常是通过调用 Bean 的构造方法来创建 Bean 对象的过程。
  4. 设置 Bean 的属性:一旦 Bean 对象被实例化,Spring 容器会根据配置文件中的属性设置,通过调用相应的 setter 方法来设置 Bean 的属性。
  5. Bean 的初始化:如果配置了初始化方法(例如通过 init-method 属性),Spring 容器会在 Bean 的所有属性设置完成后调用初始化方法。
  6. Bean 的使用:一旦所有 Bean 实例化、属性设置和初始化都完成,Bean 就可以被应用程序使用了。
  7. Bean 的销毁:如果配置了销毁方法(例如通过 destroy-method 属性),Spring 容器在应用程序关闭时会调用 Bean 的销毁方法进行清理工作。

这是 Spring Bean 的一般实例化流程,具体的实现细节可能会根据不同的配置方式和 Bean 类型而有所不同。希望这个回答能够帮助你理解 Spring Bean 的实例化过程。

Bean生命周期

在 Spring 框架中,Bean 的生命周期可以分为以下阶段:

  1. 实例化阶段
  2. Spring 容器根据配置信息实例化 Bean 对象,通常是通过调用 Bean 的构造方法来创建实例。
  3. 属性设置阶段
  4. Spring 容器会注入 Bean 的属性,可以通过构造函数注入、Setter 方法注入或字段注入等方式来设置 Bean 的属性。
  5. 初始化阶段
  6. 如果 Bean 实现了 InitializingBean 接口,或者在配置文件中通过 init-method 指定了初始化方法,Spring 容器会在 Bean 的所有属性设置完成后调用初始化方法进行一些初始化操作。
  7. Bean 可用阶段
  8. 在初始化完成后,Bean 就可以被应用程序使用了,可以执行各种业务逻辑。
  9. 销毁阶段
  10. 当应用程序关闭时,Spring 容器会调用 Bean 的销毁方法,可以通过实现 DisposableBean 接口或在配置文件中通过 destroy-method 指定销毁方法来进行清理操作。

在整个生命周期中,Spring 容器负责管理 Bean 的创建、初始化和销毁等过程,开发人员可以通过配置文件或注解来定义 Bean 的生命周期行为,以满足不同的需求。

总的来说,Spring Bean 的生命周期可以概括为实例化、属性设置、初始化、可用和销毁等阶段。希望这个回答对你有帮助。


Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类

  • Bean自身的方法: 这个包括了Bean本身调用的方法和通过配置文件中<bean>的init-method和destroy-method指定的方法
  • Bean级生命周期接口方法: 这个包括了BeanNameAware、BeanFactoryAware、ApplicationContextAware;当然也包括InitializingBean和DiposableBean这些接口的方法(可以被@PostConstruct和@PreDestroy注解替代)
  • 容器级生命周期接口方法: 这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
  • 工厂后处理器接口方法: 这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

具体而言,流程如下

  • 如果 BeanFactoryPostProcessor 和 Bean 关联, 则调用postProcessBeanFactory方法.(即首先尝试从Bean工厂中获取Bean)
  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessBeforeInstantiation方法
  • 根据配置情况调用 Bean 构造方法实例化 Bean
  • 利用依赖注入完成 Bean 中所有属性值的配置注入
  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessAfterInstantiation方法和postProcessProperties
  • 调用xxxAware接口 (上图只是给了几个例子)
  • 第一类Aware接口
    • 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
    • 如果 Bean 实现了 BeanClassLoaderAware 接口,则 Spring 调用 setBeanClassLoader() 方法传入classLoader的引用。
    • 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
  • 第二类Aware接口
  • 如果 Bean 实现了 EnvironmentAware 接口,则 Spring 调用 setEnvironment() 方法传入当前 Environment 实例的引用。
  • 如果 Bean 实现了 EmbeddedValueResolverAware 接口,则 Spring 调用 setEmbeddedValueResolver() 方法传入当前 StringValueResolver 实例的引用。
  • 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  • ...
  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  • 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。(或者有执行@PostConstruct注解的方法)
  • 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
  • 如果在 <bean> 中指定了该 Bean 的作用范围为 scope="singleton",则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用范围为 scope="prototype",则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  • 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;(或者有执行@PreDestroy注解的方法)
  • 如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。


Spring启动流程

https://blog.csdn.net/dreambyday/article/details/125473596

Spring 框架在启动过程中会执行一系列步骤来初始化应用程序上下文并准备好处理请求。以下是 Spring 启动的主要流程:

  1. 加载配置文件:Spring 应用程序启动时会加载配置文件,包括 XML 配置文件、注解配置类等。
  2. 创建应用程序上下文:Spring 容器在启动时会创建应用程序上下文,该上下文包含了应用程序中所有 Bean 的定义、依赖关系和配置信息。
  3. 扫描组件:Spring 容器会扫描应用程序中的组件,包括注解标记的 Bean、配置类等,并将它们注册到应用程序上下文中。
  4. 实例化 Bean:Spring 容器根据配置信息实例化 Bean 对象,可以通过构造函数注入、Setter 方法注入或字段注入等方式来设置 Bean 的属性。
  5. 依赖注入:Spring 容器会自动解析 Bean 之间的依赖关系,并将依赖的 Bean 注入到目标 Bean 中。
  6. 执行 Bean 的生命周期回调:如果 Bean 实现了特定的接口(如 InitializingBean 和 DisposableBean),Spring 容器会在适当的时机调用 Bean 的初始化方法和销毁方法。
  7. 执行自定义初始化方法:如果在配置文件中通过 @PostConstruct 注解或 init-method 指定了初始化方法,Spring 容器会在实例化和依赖注入完成后调用这些方法。
  8. 应用程序准备就绪:当所有 Bean 实例化、依赖注入和初始化完成后,应用程序就处于可用状态,可以处理请求和执行业务逻辑。
  9. 处理请求:Spring 应用程序启动后,可以接收和处理来自客户端的请求,调用相应的 Bean 处理请求并返回结果。

总的来说,Spring 启动流程包括加载配置文件、创建应用程序上下文、注册 Bean、依赖注入、执行生命周期回调等步骤,最终使应用程序准备好接收和处理请求。

AOP

核心概念

  1. Jointpoint(连接点):具体的切面点点抽象概念,可以是在字段、方法上,Spring中具体表现形式是PointCut(切入点),仅作用在方法上。
  2. Advice(通知): 在连接点进行的具体操作,如何进行增强处理的,分为前置、后置、异常、最终、环绕五种情况。
  3. 目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。
  4. AOP代理:AOP框架创建的对象,简单的说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。
  5. Weaving(织入):将增强处理添加到目标对象中,创建一个被增强的对象的过程

总结为一句话就是:在目标对象(target object)的某些方法(jointpoint)添加不同种类的操作(通知、增强操处理),最后通过某些方法(weaving、织入操作)实现一个新的代理目标对象。


原理

Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。


实现 AOP 的技术,主要分为两大类:

  • 静态代理指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;
  • 编译时编织(特殊编译器实现)
  • 类加载时编织(特殊的类加载器实现)。
  • 动态代理在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
  • JDK 动态代理
    • JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
    • Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新,Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
    • JDK Proxy 是通过拦截器加反射的方式实现的;
    • JDK Proxy 只能代理实现接口的类;
    • JDK Proxy 实现和调用起来比较简单;
  • CGLIB
  • CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  • CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。


使用场景

  1. 日志打印
  2. 共同业务处理
  3. 监控
  4. 事务


Spring中的bean的作用域有哪些?

  1. singleton:唯一bean实例,Spring中的bean默认都是单例的。
  2. prototype:每次请求都会创建一个新的bean实例。
  3. request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  4. session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
  5. global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。


Spring mvc工作原理

流程说明:

1.客户端(浏览器)发送请求,直接请求到DispatcherServlet。

2.DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。

3.解析到对应的Handler(也就是我们平常说的Controller控制器)。

4.HandlerAdapter会根据Handler来调用真正的处理器来处理请求和执行相对应的业务逻辑。

5.处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是逻辑上的View。

6.ViewResolver会根据逻辑View去查找实际的View。

7.DispatcherServlet把返回的Model传给View(视图渲染)。

8.把View返回给请求者(浏览器)。


如何自定义Spring Boot Starter?

  • 实现功能
  • 添加Properties
@Data@ConfigurationProperties(prefix="com.pdai")
public class DemoProperties {
    private String version;
    private String name;
}
  • 添加AutoConfiguration
@Configuration@EnableConfigurationProperties(DemoProperties.class)
public class DemoAutoConfiguration {
    @Bean
    public com.pdai.demo.module.DemoModule demoModule(DemoProperties properties){
        com.pdai.demo.module.DemoModule demoModule=new com.pdai.demo.module.DemoModule();
        demoModule.setName(properties.getName());
        demoModule.setVersion(properties.getVersion());return demoModule;
    }
}
  • 添加spring.factory

在META-INF下创建spring.factory文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pdai.demospringbootstarter.DemoAutoConfiguration
  • install

了应对在SpringBoot中的高并发及优化访问速度,我们一般会把页面上的数据查询出来,然后放到redis中进行缓存。减少数据库的压力。

在SpringBoot中一般使用

thymeleafViewResolver.getTemplateEngine().process("goodlist", ctx);

进行页面的渲染,而这个ctx就是SpringWebContext对象,我们一般进行如下获取:

SpringWebContext swc=new SpringWebContext(request,response,request.getServletContext(),request.getLocale(),model.asMap(),applicationContext);

在SpringBoot 1.X的版本中以上代码可以使用。但在SpringBoot 2.0中,就无法找到SpringWebContext了。那应该如何去解决这个问题呢?

说一下我的思路,.process方法中ctx所在参数所需要的类型为接口IContext



image

也就是需要有实现了IContext的类就可以了,然后进入IContext接口找所有的实现类



image

然后看到WebContext似乎有些像上面所用的SpringWebContext。即做出如下改变,完美实现了thymeleaf的页面渲染。

WebContext ctx=new WebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap());

html=thymeleafViewResolver.getTemplateEngine().process("goodlist", ctx);

在SpringBoot 2.0中使用上述代码,可以完全替代。

(当然在下不才,暂时只找到了这种办法,在网络上也没找到对应的比较不错的策略。所以分享出来,以备分享出来,帮助遇到此问题的程序员们。如果大家有什么更好的处理办法可以一起互相交流哦)

目前我正在搞基于SpringBoot、Redis、消息队列的秒杀小项目,主要还是为了梳理如何解决高并发的问题过程。

GitHub:https://github.com/iquanzhan/SecKillShop

欢迎点击Start哦

所用技术

1.后端:SpringBoot、JSR303、MyBatis

2.前端:Thymeleaf、BootStrap、Jquery

3.中间件:RabbitMQ、Redis、Druid

Spring框架对于Java后端程序员来说再熟悉不过了,以前只知道它用的反射实现的,但了解之后才知道有很多巧妙的设计在里面。如果不看Spring的源码,你将会失去一次和大师学习的机会:它的代码规范,设计思想很值得学习。我们程序员大部分人都是野路子,不懂什么叫代码规范。写了一个月的代码,最后还得其他老司机花3天时间重构,相信大部分老司机都很头疼看新手的代码。

废话不多说,我们进入今天的正题,在Web应用程序设计中,MVC模式已经被广泛使用。SpringMVC以DispatcherServlet为核心,负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。想要实现自己的SpringMVC框架,需要从以下几点入手:

一、了解SpringMVC运行流程及九大组件

二、梳理自己的SpringMVC的设计思路

三、实现自己的SpringMVC框架

一、了解SpringMVC运行流程及九大组件

1、SpringMVC的运行流程

⑴ 用户发送请求至前端控制器DispatcherServlet

⑵ DispatcherServlet收到请求调用HandlerMapping处理器映射器。

⑶ 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

⑷ DispatcherServlet通过HandlerAdapter处理器适配器调用处理器

⑸ 执行处理器(Controller,也叫后端控制器)。

⑹ Controller执行完成返回ModelAndView

⑺ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet

⑻ DispatcherServlet将ModelAndView传给ViewReslover视图解析器

⑼ ViewReslover解析后返回具体View

⑽ DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。

⑾ DispatcherServlet响应用户。

从上面可以看出,DispatcherServlet有接收请求,响应结果,转发等作用。有了DispatcherServlet之后,可以减少组件之间的耦合度。

2、SpringMVC的九大组件(ref:【SpringMVC】9大组件概览)

protected void initStrategies(ApplicationContext context) {	//用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File.
initMultipartResolver(context);	//SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
initLocaleResolver(context); 
//用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、
//如图片、css样式等。SpringMVC的主题也支持国际化, 
initThemeResolver(context);	//用来查找Handler的。
initHandlerMappings(context);	//从名字上看,它就是一个适配器。Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。
//如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情
initHandlerAdapters(context);	//其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?
//这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。
initHandlerExceptionResolvers(context);	//有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,
//如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。
initRequestToViewNameTranslator(context);	//ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。
//View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。
initViewResolvers(context);	//用来管理FlashMap的,FlashMap主要用在redirect重定向中传递参数。
initFlashMapManager(context); 
}

二、梳理SpringMVC的设计思路

本文只实现自己的@Controller、@RequestMapping、@RequestParam注解起作用,其余SpringMVC功能读者可以尝试自己实现。

1、读取配置

从图中可以看出,SpringMVC本质上是一个Servlet,这个 Servlet 继承自 HttpServlet。FrameworkServlet负责初始化SpringMVC的容器,并将Spring容器设置为父容器。因为本文只是实现SpringMVC,对于Spring容器不做过多讲解(有兴趣同学可以看看我另一篇文章:向spring大佬低头--大量源码流出解析)。

为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息。通过web.xml中加载我们自己写的MyDispatcherServlet和读取配置文件。

2、初始化阶段

在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:

  • 加载配置文件
  • 扫描用户配置包下面所有的类
  • 拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
  • 初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出

3、运行阶段

每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。按顺序包括以下功能:

  • 异常的拦截
  • 获取请求传入的参数并处理参数
  • 通过初始化好的handlerMapping中拿出url对应的方法名,反射调用

三、实现自己的SpringMVC框架

工程文件及目录:

首先,新建一个maven项目,在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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.liugh</groupId>
 <artifactId>liughMVC</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>war</packaging>
 
<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>
<java.version>1.8</java.version>
</properties>
<dependencies>
 <dependency>
 	 <groupId>javax.servlet</groupId> 
 <artifactId>javax.servlet-api</artifactId> 
 <version>3.0.1</version> 
 <scope>provided</scope>
</dependency>
 </dependencies></project>

接着,我们在WEB-INF下创建一个web.xml,如下配置:

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>MySpringMVC</servlet-name>
<servlet-class>com.liugh.servlet.MyDispatcherServlet</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>MySpringMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping></web-app>

application.properties文件中只是配置要扫描的包到SpringMVC容器中。

scanPackage=com.liugh.core

创建自己的Controller注解,它只能标注在类上面:

package com.liugh.annotation;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)@Documentedpublic @interface MyController { /**
 * 表示给controller注册别名
 * @return
 */
 String value() default "";
}

RequestMapping注解,可以在类和方法上:

package com.liugh.annotation;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)@Documentedpublic @interface MyRequestMapping { /**
 * 表示访问该方法的url
 * @return
 */
 String value() default "";
}

RequestParam注解,只能注解在参数上

package com.liugh.annotation;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)@Documentedpublic @interface MyRequestParam { /**
 * 表示参数的别名,必填
 * @return
 */
 String value();
}

然后创建MyDispatcherServlet这个类,去继承HttpServlet,重写init方法、doGet、doPost方法,以及加上我们第二步分析时要实现的功能:

package com.liugh.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.liugh.annotation.MyController;import com.liugh.annotation.MyRequestMapping;public class MyDispatcherServlet extends HttpServlet{ 
private Properties properties=new Properties();	
private List<String> classNames=new ArrayList<>();	
private Map<String, Object> ioc=new HashMap<>();	
private Map<String, Method> handlerMapping=new HashMap<>();	
private Map<String, Object> controllerMap=new HashMap<>();	
@Override
public void init(ServletConfig config) throws ServletException {	
//1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));	
//2.初始化所有相关联的类,扫描用户设定的包下面所有的类
doScanner(properties.getProperty("scanPackage"));	
//3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) beanName默认是首字母小写
doInstance();	
//4.初始化HandlerMapping(将url和method对应上)
initHandlerMapping();
}	
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.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");
}
}	
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { if(handlerMapping.isEmpty()){	return;
}
String url=req.getRequestURI();
String contextPath=req.getContextPath();
url=url.replace(contextPath, "").replaceAll("/+", "/");	
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 NOT FOUND!");	return;
}
Method method=this.handlerMapping.get(url);	
//获取方法的参数列表
Class<?>[] parameterTypes=method.getParameterTypes();	
//获取请求的参数
Map<String, String[]> parameterMap=req.getParameterMap();	
//保存参数值
Object [] paramValues=new Object[parameterTypes.length];	
//方法的参数列表
 for (int i=0; i<parameterTypes.length; i++){ 
 //根据参数名称,做某些处理 
 String requestParam=parameterTypes[i].getSimpleName(); 
 
 
 if (requestParam.equals("HttpServletRequest")){ 
 //参数类型已明确,这边强转类型 
 	paramValues[i]=req; continue; 
 } 
 if (requestParam.equals("HttpServletResponse")){ 
 	paramValues[i]=resp; continue; 
 } if(requestParam.equals("String")){ for (Entry<String, String[]> param : parameterMap.entrySet()) {
 	String value=Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
 	paramValues[i]=value;
 	}
 }
 } 
//利用反射机制来调用
try {
method.invoke(this.controllerMap.get(url), paramValues);//第一个参数是method所对应的实例 在ioc容器中
} catch (Exception e) {
e.printStackTrace();
}
}	private void doLoadConfig(String location){	//把web.xml中的contextConfigLocation对应value值的文件加载到流里面
InputStream resourceAsStream=this.getClass().getClassLoader().getResourceAsStream(location); try {	//用Properties文件加载文件里的内容
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}finally {	//关流
if(null!=resourceAsStream){	try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}	
private void doScanner(String packageName) {	//把所有的.替换成/
URL url=this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
File dir=new File(url.getFile());	for (File file : dir.listFiles()) { if(file.isDirectory()){	//递归读取包
doScanner(packageName+"."+file.getName());
}else{
String className=packageName +"." +file.getName().replace(".class", "");
classNames.add(className);
}
}
}	
private void doInstance() {	if (classNames.isEmpty()) {	return;
}	
for (String className : classNames) {	try {	//把类搞出来,反射来实例化(只有加@MyController需要实例化)
Class<?> clazz=Class.forName(className); if(clazz.isAnnotationPresent(MyController.class)){
ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
}else{	continue;
}
} catch (Exception e) {
e.printStackTrace();	continue;
}
}
}	private void initHandlerMapping(){	if(ioc.isEmpty()){	return;
}	try {	for (Entry<String, Object> entry: ioc.entrySet()) {
Class<? extends Object> clazz=entry.getValue().getClass(); if(!clazz.isAnnotationPresent(MyController.class)){	continue;
}	
//拼url时,是controller头的url拼上方法上的url
String baseUrl="";	if(clazz.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping annotation=clazz.getAnnotation(MyRequestMapping.class);
baseUrl=annotation.value();
}
Method[] methods=clazz.getMethods();	for (Method method : methods) { if(!method.isAnnotationPresent(MyRequestMapping.class)){	continue;
}
MyRequestMapping annotation=method.getAnnotation(MyRequestMapping.class);
String url=annotation.value();
url=(baseUrl+"/"+url).replaceAll("/+", "/");
handlerMapping.put(url,method);
controllerMap.put(url,clazz.newInstance());
System.out.println(url+","+method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}	/**
 * 把字符串的首字母小写
 * @param name
 * @return
 */
private String toLowerFirstWord(String name){ char[] charArray=name.toCharArray();
charArray[0] +=32;	return String.valueOf(charArray);
}
}

这里我们就开发完了自己的SpringMVC,现在我们测试一下:

package com.liugh.core.controller;import java.io.IOException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.liugh.annotation.MyController;import com.liugh.annotation.MyRequestMapping;import com.liugh.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();
 }
 }
}

访问http://localhost:8080/liughMVC/test/doTest?param=liugh如下:

访问一个不存在的试试:

到这里我们就大功告成了!

我自己在腾讯课堂上也有讲过一堂手写springMVC的直播分享,有感兴趣的可以来看看

https://pan.baidu.com/s/17v3syshIGQWjCHL0yi73Cg

1,3分钟读懂Spring核心源码;

2,SpringMVC与Spring框架关系;

3,SpringMVC的所有注解定义实战;

4,手写SpringMVC框架实战;

5,Tomcat加载进行测试实战;

6,互动答疑。

如果感兴趣的话可以来找我获取其他的资料 想学习提升自己的私信我【JAVA架构】获取往期Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术 记得转发下哦

来自同事James的一篇文章