Java开发从工作到原理--HTTP API接口开发

Java开发从工作到原理--BasicErrorController统一异常处理中我们通过TestController类实现了一个http接口,通过浏览器可以访问,代码非常简单:

@Controller注解表明这个类需要托管给Spring,因为@Controller注解继承了@Component注解,而标有@Component注解得类会由ClassPathBeanDefinitionScanner对象进行包扫描时进行加载解析,以及BeanDefinition得注册,这里ClassPathBeanDefinitionScanner是通过TypeFilter来进行bean是否需要加载解析的判断。而ClassPathBeanDefinitionScanner的对象是在SpringApplication.run方法中调用createApplicationContext方法创建AnnotationConfigServletWebApplicationContext对象时,由BeanUtils.instantiateClass()方法调用AnnotationConfigServletWebApplicationContext的无参构造函数时,在构造函数中创建的。

默认未对ClassPathBeanDefinitionScanner构造函数中的useDefaultFilters参数进行设置,useDefaultFilters使用默认值true,在构造函数中会进行默认TypeFilter的设置

registerDefaultFilters()方法为ClassPathBeanDefinitionScanner父类ClassPathScanningCandidateComponentProvider中的方法

这里不仅注册了@Component注解,还注册了@ManagedBean,@Named注解,标有这三个注解的bean被扫描到时会被解析加载注册BeanDefinition,但这个并不绝对。因为ClassPathScanningCandidateComponentProvider中既有includeFilters也有excludeFilters,进行判断时会先用excludeFilters进行判断,然后使用includeFilters进行判断

TypeFilter中会直接追溯到元注解然后进行判断,所以同样能够对标有@Controller,@Service,@Repository注解的类进行扫描加载,@Configuration注解也是继承@Component注解的。

@RequestMapping在这里主要用了其请求地址配置映射的功能,还有很多属性可供我们配置:

name: 对mapping进行命名。

path&value: 配置映射路径,注意path&value返回的都是String[] 表明同一个方法可以同时映射多个路径。

method:  配置允许访问的Http请求方法,返回为RequestMethod[] 表明可以允许多个方法访问,类型枚举为RequestMethod,共有8中,若未配置表示允许所有方法访问。

params: 配置表示当请求参数中有对应的参数名和参数值时才允许进入方法,支持 "myParam=myValue"形式和“myParam!=myValue"形式,返回为String[]表示可多个。

headers: 配置表示当请求头中有对应的请求头和值时才允许进入方法同样支持 "My-Header=myValue"形式和“My-Header!=myValue"形式,返回为String[]表示可多个。

consumes: 配置表示请求header中的Content-Type的值有对应的类型时才允许进入方法,返回String[] 表示可以配置多个,枚举类型为MediaType。

produces: 配置表示接口返回的数据类型,返回String[] 表示可以配置多个,枚举类型为MediaType。

@RequestMapping的Target,为type和method,@RequestMapping标注在type上时,其配置的属性可以被标注在方法上的@RequestMapping继承。

还有一套另外的注解@PostMapping @GetMapping @DeleteMapping @PutMapping @PatchMapping都是继承了@RequestMapping,只是配置好了method属性为对应的Http请求方法,RequestMethod枚举中还有三种方法,OPTIONS,TRACE,HEAD其中OPTIONS方法常见于跨域请求,用于判断服务器是否允许跨域请求,TRACE和HEAD方法比较少见。

Java开发从工作到原理--BasicErrorController统一异常处理我们还看到http请求的处理初始的解析工作依然是交给Tomcat,后续则通过DispatcherServlet进行处理,包括handlerMapping,handlerAdapter,viewResolver的相关处理。

很明显DispatcherServlet符合J2EE的Servlet标准,即继承了HttpServlet接口,虽然中间有FrameworkServlet,HttpServletBean抽象类实现。

Java开发从工作到原理--SpringBoot如何启动内置Tomcat中我们提到SpringBoot内置Tomcat启动时会通过ServletContextInitializer进行Servlet,Filter,Listener,Session的配置。而ServletContextInitializer的实现类中有DispatcherServletRegistrationBean专门用于DispatcherServlet的注册,在DispatcherServletAutoConfiguration类中可以看到DispatcherServlet的注册和DispatcherServletRegistrationBean 的注册,这里并未进行DispatcherServlet中HandlerMapping,HandlerAdapter等等属性的设置,而在WebMvcAutoConfiguration中进行了默认的一些RequestMappingHandlerMapping,WelcomePageHandlerMapping,RequestMappingHandlerAdapter,BeanNameViewResolver,InternalResourceViewResolver的配置,在项目启动后DispatcherServlet接收到第一个请求时,触发DispatcherServlet servlet部分功能的初始化,这时会将这些HandlerMapping对象,HandlerAdapter对象,ViewResolver对象与DispatcherServlet进行关联。

加断点调试可以看到对应的调用栈:

GenericServlet为HttpServlet的父类。

以HandlerMapping为例可以查看具体关联操作为:

从beanFactory(applicationContext)中获取HandlerMapping对象并排序,如果没有使用DispatcherServlet.properties中配置的HandlerMapping对象。

Spring 中使用RequestMappingHandlerMapping进行@Controller bean的@RequestMapping注解解析,具体的解析工作由RequestMappingHandlerMapping的InitializingBean接口方法,在bean生命周期中处理。

父类中的方法内容为

processCandidateBean中isHandler方法进行是否有@Controller,@RequestMapping判断,解析由detechHandlerMethods方法处理

查看isHandler方法的实现

可以看到常用的另一套接口开发体系为Endpoint体系,是由WebMvcEndpointHandlerMapping进行相关处理,这里不多讲。

回到RequestMappingHandlerMapping的isHandler方法中其实很简单

 

detechHandlerMethods方法中主要是处理带有@RequestMapping的方法的相关配置,处理与标注在Class上@RequestMapping配置属性的继承关系获得最终的配置信息,最后将解析出来的信息注册到RequestMappingHandlerMapping的MappingRegistry中以供接收到请求时用于判断查找对应的请求处理方法。简单的说是生成请求URL和Controller中的方法的对应关系。

接下来是请求处理过程,网上找的图来自(Spring MVC 请求处理流程):

具体代码不做讲解,在DispatcherServlet的doDispatch方法中,主要介绍整个过程中的几个扩展点;

HandlerExecutionChain中的HandlerInterceptor

拦截器,常用于进行用户是否已登录,是否有权限调用目前调用的接口判断拦截。常规的使用方式为定义一个类实现HandlerInterceptor接口,然后在HandlerInterceptor的接口方法中进行相关处理,然后通过实现WebMvcConfigurer的addInterceptors方法进行Interceptor注册。SpringMVC通过依赖注入的方式将BeanFacotry中的所有WebMvcConfigurer注入到EnableWebMvcConfiguration(继承DelegatingWebMvcConfiguration)中,然后通过处理将所有HandlerInterceptor从WebMvcConfigurer中取出交给RequestMappingHandlerMapping。这个机制也就意味着我们可以自定义HandlerInterceptor + WebMvcConfigurer来完成HandlerInterceptor的注册,以供使用。

当然还有一些比较特殊的HandlerInterceptor,从RequestMappingHandlerMapping中获得HandlerExecutionChain的方法追溯到AbstractHandlerMapping中的getHandler方法和getHandlerExecutionChain方法

所有用到的HandlerInterceptor都来自this.adaptedInterceptors属性,而this.adaptedInterceptors的值是在initApplicationContext方法中被修改

在EnableWebMvcConfiguration中,配置在WebMvcConfigurer中的HandlerInterceptor设置到RequestMappingHandlerMapping中是放到this.interceptors属性的,上面的extendInterceptors方法一个空方法,供继承AbstractHandlerMapping的类进行扩展使用,而detechMappedInterceptors方法则开通了另一种注册HandlerInterceptor的方法:

从BeanFactory中获取所有MappedInterceptor对象,并添加到this.adaptedInterceptors中,而MappedInterecptor类实现了HandlerInterceptor接口,MappedInterecptor可以对我们自定义的HandlerInterceptor进行增强,可以配合pathMatcher进行判断是否需要将MappedInterceptor中的HandlerInterceptor对象添加到HandlerExecutionChain中,而pathMatcher的引入需要依靠PathMatchConfigurer,可以通过WebMvcConfigurer.configurePathMatch方法进行配置。

总的来说MappedInterceptor的注册相对HandlerInterceptor简单,配合PathMatcher使用,只需在自定义HandlerInterceptor的同时,确定需要拦截URL的includePatterns正则表达式和不需要拦截URL的excludePatterns正则表达式即可,可以在Configuration类中通过@Bean进行注册。

2、HandlerAdapter进行方法调用时的扩展点

异步调用

异步化在这里主要指的是整个请求的处理至少经过两个线程。这里经过两个线程的意思是指从同一个或者两个不同的线程池中获取了两次线程Worker,可能会出现两个线程是同一个线程池中同一个线程的情况,但是只是分两次获取,得到的是同一个线程。

方法异步调用分两种形式:

 显式的设置异步

显示的设置异步实际上跟SpringMVC并没有多少关系,是基于动态代理技术实现的一种功能,需要在引导启动类上设置@EnableAsync,且在需要异步的方法上增加@Async注解。

未增加@EnableAsync和@Async 修改TestController类增加日志输出,发送http请求打印日志如下:

输出日志线程为http请求处理线程;

增加@EnableAsync和@Async后:

日志打印线程为applicationTaskExecutor线程池中线程。在未配置AsyncTaskExecutor时,默认从BeanFactory获取TaskExecutor接口对象进行任务执行,AsyncTaskExecutor可以通过AsyncConfigurer进行配置,将AsyncConfigurer对象托管给Spring即可。

注意点:spring boot 直到2.0.x版本默认配置的TaskExecutor,默认线程数为1个,2.1.x版本开始默认线程数改为8,还在使用低版本spring boot的同学记得进行配置,以免执行定时任务时出现任务排队的情况。

至于@Async功能的实现原理,不用说肯定是AOP啦,不多说了。

另一个注意点:尽量不要在标有@Transactional的方法中调用标有@Async的数据库操作方法,Spring事务机制会在开启事务后将数据库连接放到ThreadLocal中,同一个线程内的数据库操作才能拿到同一个connection,对应同一个事务,除非@Asynce方法不需要使用@Transactional对应的事务,否则会出现事务部分有效的情况。

通过返回类型控制为异步

在Controller中,一般的请求我们可以返回ModelAndView,String来对应相关的视图,由ViewResolver进行处理,我们也可以返回json数据,只需要在方法上添加@ResponseBody注解,或者在Controller类上添加@RestController注解(集合了@Controller和@ResponseBody),如果要开启异步最简单明了的是以Callable返回

,其次还有ResponseBodyEmitter,StreamingResponseBody,DeferredResult,WebAsyncTask等类型,在下篇博客中将会对这些类的使用做相关讲解。

请求参数的获取及校验

既然可以通过接口返回json数据,那么当然也可以通过接口接收json数据,只要在方法参数前添加@RequestBody即可接收json数据请求,json请求参数可以直接转换为类对象。

从请求地址中获取请求数据会用到@PathVariable,

从Http Header中获取数据会用到@RequestHeader,

从HttpSession中获取数据会用到@SessionAttributes,@SessionAttribute,

从cookie中获取数据会用到@CookieValue,

正常的form表单请求参数获取可使用@RequestParam,

直接在方法参数中添加HttpServletRequest和HttpServletResponse参数可获取当前请求的这两个对象。

参数校验通@Validated注解表明需要进行参数校验,实际上返回值也可以进行校验,会在以后弄一篇博客用于讲解。

 

重定向和跳转

当我们直接返回一个String指定对应的试图时,可以在之前添加"forward:"表示进行跳转,添加"redirect:"表示进行重定向,会引起浏览器不同的反应。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值