springboot控制器找不到视图_SpringBoot视图控制器和拦截器

视图控制器

视图控制器,也就是我们所说的viewController,不用通过在Controller中增加映射方法的情况下,通过简单的设置就可以把前端指定的请求映射到相应的页面。接下来我们还是以上一个employee-management项目为例,可以看到我们登入首页(login.html)和主面板页(dashboard.html)的请求都在Controller中有对应的方法

    @GetMapping("/login")    public String login(){        return "login";    }    @GetMapping("/dashboard")    public String dashboard(){        return "dashboard";    }

这样一来当请求过多时,就会有大量的与业务无关的代码被写在Controller中,因此来借助SpringBoot的ViewController来代替Controller中这些代码

首先在项目下新建一个config的包,在包下创建一个自己的WebMvcConfig配置类,该类要实现WebMvcConfigurer接口,代码如下

@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {    @Override    public void addViewControllers(ViewControllerRegistry registry) {        registry.addViewController("/").setViewName("/login");        registry.addViewController("/login.html").setViewName("/login");        registry.addViewController("/dashboard.html").setViewName("/dashboard");    }}

然后把我们LoginController中的login和dashboard方法注释掉,可以看到我们无论是通过"localhost:8080/"还是"localhost:8080/login.html"都可以访问我们登入页。

7005f37da1968ef1151aad5c3d3df6ca.png

同时输入用户名、密码(123456)也可以正常访问主面板页和员工列表页,到这里我们就实现了通过ViewController等价替换Controller中编写方法实现请求到视图映射的功能。

3ed6add3bfdcaa126d1b179127ed1259.png

同时我们也看到WebMvcConfigurer还有一些其他方法,如下

    // 格式化器    default void addFormatters(FormatterRegistry registry) {}    // 拦截器    default void addInterceptors(InterceptorRegistry registry) {}    // 视图解析器    default void configureViewResolvers(ViewResolverRegistry registry) {}

登入成功就来到了主面板页(http://localhost:8080/dashboard.html),此时我们把链接放入其他浏览器,发现也可以正常访问,这就失去了我们登入功能的意义,接下来将通过注册拦截器来实现登入后才能访问的功能。

拦截器

接下来我们将通过WebMvcConfigurer的addInterceptors()方法向项目中注册拦截器。

首先定义自己的拦截器类LoginHandlerInterceptor实现HandlerInterceptor接口,HandlerInterceptor接口有3个方法,如下

public interface HandlerInterceptor {    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        return true;    }    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}}

接下来我们重写preHandle方法,来实现请求的预处理,大致思路是先从session中拿登入用户信息,如果能拿到说明以登入可以继续访问,否则跳转登入页,并携带提示用户没有权限等登入的信息,代码如下

public class LoginHandlerInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)        throws Exception {        Object loginUser = request.getSession().getAttribute("loginUser");        if(loginUser == null){            request.setAttribute("msg","没有登入权限");            request.getRequestDispatcher("/login.html").forward(request,response);            return false;        }        return true;    }}

这样我们自己的拦截器类就开发完成了,接下来我们把这个拦截器添加到项目中,在WebMvcConfig类中重写addInterceptors方法,如下

    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new LoginHandlerInterceptor()).excludePathPatterns("/login","/login.html","/user/login","/assets/**");    }

    同时拦截器需要放过我们登入页、登入请求、静态资源访问的请求,我们用excludePathPatterns方法中添加想要放行的请求,否则我们将不能访问任何页面了。接下来我们看一下效果,在未登入状态下访问我们主面板页,直接跳转到了登入页并且提示“没有登入权限”,一切正常。

a611348f7b2727a0839fa6deeb3204c0.png

SpringBoot对WebMvcConfig的加载过程

首先在我们的addInterceptors和addViewControllers两个方法上分别打上断点,然后debug我们这个项目,如下:

841a0db164c85ac56847417ce5a5564d.png

依次在上图上标识出了三个部分:断点位置、变量区、调试过程区,接下来我们重点看一下调试过程和变量区,关键过程如下:

首先是从主入口的run方法进来

SpringApplication.run(EmployeeManagementApplication.class, args);

接下来就到了我们的刷新上下文

public void refresh() throws BeansException, IllegalStateException {    synchronized(this.startupShutdownMonitor) {        // 准备刷新        this.prepareRefresh();        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();        // 准备BeanFactory以便在上下文中使用        this.prepareBeanFactory(beanFactory);        try {            this.postProcessBeanFactory(beanFactory);            this.invokeBeanFactoryPostProcessors(beanFactory);            this.registerBeanPostProcessors(beanFactory);            this.initMessageSource();            this.initApplicationEventMulticaster();            this.onRefresh();            this.registerListeners();            // 完成BeanFactory的初始化,加载项目中非懒加载的Bean,单例模式            this.finishBeanFactoryInitialization(beanFactory);            this.finishRefresh();        } catch (BeansException var9) {            if (this.logger.isWarnEnabled()) {                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);            }            this.destroyBeans();            this.cancelRefresh(var9);            throw var9;        } finally {            this.resetCommonCaches();        }    }}

在BeanFactory初始化的过程中,从beanDefinitionNames拿到了需要初始化的beanNames,其中有一个名称为requestMappingHandlerMapping的bean,如下

47d00c44c7f6c6a3b550c16264d036e4.png

之后通过代理模式在WebMvcAutoConfiguration.class中找到了bean注册的位置

@Bean@Primarypublic RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {    return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService, resourceUrlProvider);}// 接下来我们看父类中的requestMappingHandlerMapping方法mapping.setInterceptors(this.getInterceptors(conversionService, resourceUrlProvider));// 这个getInterceptors具体代码如下protected final Object[] getInterceptors(FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {    if (this.interceptors == null) {        InterceptorRegistry registry = new InterceptorRegistry();        // 这个方法遍历所有的delegates调用他们的addInterceptors方法注册拦截器        this.addInterceptors(registry);        // 在这里补充一下addInterceptors方法        // public void addInterceptors(InterceptorRegistry registry) {        //   Iterator var2 = this.delegates.iterator();        //   while(var2.hasNext()) {        //       WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();        //       delegate.addInterceptors(registry);        //   }        // }                      registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));        registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));        // 获取registry中的拦截器,并存入interceptors数组中,这个在请求前置拦截时会用到        this.interceptors = registry.getInterceptors();    }    return this.interceptors.toArray();}

其中delegates数组中就有我们自己定义的WebMvcConfig类

130be0ce2cff62375596441896d14c21.png

@Overridepublic void addInterceptors(InterceptorRegistry registry) {   registry.addInterceptor(new LoginHandlerInterceptor()).excludePathPatterns("/login","/login.html","/user/login","/assets/**");}

那拦截器是怎么生效的呢,项目完全启动起来,我们直接访问我们的员工列表页面(http://localhost:8080/emps),看一下请求路径是怎么样的,首先我们从DispatchServlet类的doDispatch方法开始看起,先看一张大图辅助理解

fdbef4d3cbda03163c236648976fc66c.png

我们本次分析主要是HandlerInterceptor部分,其他的后续有涉及咱们会再依次介绍。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {     ...     ...     try {         ...         // 通过handlerMapping获取HandlerExecutionChain,其中就有RequestMappingHandlerMapping         mappedHandler = this.getHandler(processedRequest);         // 把getHanlder方法放在这里方便大家阅读             @Nullable             protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {               if (this.handlerMappings != null) {                   // 其中handlerMappings中的RequestMappingHandlerMapping的interceptors中就有我们的LoginHandlerInterceptor,因此在后续中就会调用LoginHandlerInteceptor的preHandle方法                 for (HandlerMapping mapping : this.handlerMappings) {                   HandlerExecutionChain handler = mapping.getHandler(request);                   if (handler != null) {                     return handler;                   }                 }               }               return null;             }         ...         ...         // 调用获取到的mapperHandler的PreHandle方法,调用时机是controller处理之前         if (!mappedHandler.applyPreHandle(processedRequest, response)) {             return;         }         // 真正去调用处理程序,我们注册的viewController等都会在这里被生效.         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());         ...         ...         this.applyDefaultViewName(processedRequest, mv);         // 调用获取到的mapperHandler的PostHandle方法,调用时机是controller处理之后         mappedHandler.applyPostHandle(processedRequest, response, mv);     } catch (Exception var20) {         dispatchException = var20;     }     ...}

因此也就能够顺利的向项目中注册我们自己定义的拦截器了。

同样在BeanFactory初始化的过程中,拿到了beanName为viewControllerHandlerMapping,注册bean的代码如下

@Bean@Nullablepublic HandlerMapping viewControllerHandlerMapping(@Qualifier("mvcPathMatcher") PathMatcher pathMatcher, @Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {    ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);    this.addViewControllers(registry);    AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();    if (handlerMapping == null) {        return null;    } else {        handlerMapping.setPathMatcher(pathMatcher);        handlerMapping.setUrlPathHelper(urlPathHelper);        handlerMapping.setInterceptors(this.getInterceptors(conversionService, resourceUrlProvider));        handlerMapping.setCorsConfigurations(this.getCorsConfigurations());        return handlerMapping;    }}

同样是遍历delegates调用各delegate的addViewControllers方法,同时能够顺利的向项目中注册我们自己定义的视图控制器了,同时通过getHandler获取处理请求的Handler,handler中包含请求映射的视图的名称,也就是我们设置的setViewName中的名称,这样就得到了请求到视图的映射。

@Overridepublic void addViewControllers(ViewControllerRegistry registry) {    registry.addViewController("/").setViewName("/login");    registry.addViewController("/login.html").setViewName("/login");    registry.addViewController("/dashboard.html").setViewName("/dashboard");}

请求的详细处理过程,我们后续再给大家介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值