一、先让我们来看看异常信息

异常信息

二、在解决异常前我们先需要知道的事情

1、Web组件加载顺序

首先是:ContextLoaderListener初始化,创建Spring的IOC容器
其次是:DelegatingFilterProxy初始化,查找IOC容器,查找Bean
最后是:DispatcherServlet初始化,创建SpringMVC的IOC容器

2、有两个IOC容器

① Spring的IOC容器

Spring的IOC容器装配的 Bean 有 Mapper 和 Service ,因此当 SpringSecurity 加载到 Spring 的IOC容器时只对 Mapper 和 Service 生效
Spring的IOC容器

②SpringMVC的IOC容器

SpringMVC的IOC容器中装配的 Bean 有 Controller,所以当 SpringSecurity 加载到SpringMVC的IOC容器时就可以对 Controller 请求生效。(这也正是我们想要的效果,所以该web项目采用的SpringMVC的IOC容器)
SpringMVC的IOC容器

3、DelegatingFilterProxy初始化后查找Bean的机制

查找Bean的机制

三、问题分析

ContextLoaderListener 初始化后,springSecurityFilterChain 就在 ContextLoaderListener 创建的 Spring的IOC 容器中查找所需要的 bean,但是我们没有在 ContextLoaderListener 的 IOC 容 器中扫描 SpringSecurity 的配置类,所以 springSecurityFilterChain 对应的 bean 找不到。
问题分析

四、解决思路

方案一:把两个IOC容器合二为一

不再使用 ContextLoaderListener ,而是让 DispatcherServlet 扫描所有的 spring 配置文件。

    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-*.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
        <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <!-- url-pattern配置方式1: /表示拦截所有请求-->
        <!-- <url-pattern>/</url-pattern> -->
        
        <!-- url-pattern配置方式2:使用扩展名
            优点:1、xxx.css、xxx.png、xxx.js等静态资源不经过SpringMVC
                2、可以实现伪静态效果。即表面看上去是访问一个HTML这样一个静态页面,实际上是静JAVA代码经过运算的
                
                伪静态优点:
                    1、给黑客入侵增加难度
                    2、有利于SEO优化。(即百度、谷歌等搜索引擎更容易找我们的项目)
            缺点:违背了RestFul风格
        -->
        <url-pattern>*.html</url-pattern>

        <!-- 为什么要另外配置json扩展名呢? -->
        <!-- 如果一个ajax请求扩展名html,但实际服务器给浏览器返回的数据是json数据,会出现406错误 -->
        <!-- 为了能让Ajax请求能顺利拿到Json中得数据,配置一个.JSON的扩展名 -->
        <url-pattern>*.json</url-pattern>
    </servlet-mapping>


    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

方案二:改源码

创建同名 org.springframework.web.filter 的包名,创建同名 DelegatingFilterProxy 类 ,将源码复制粘贴到里面,再进行修改。

第一处:初始化时,直接跳过查找IOC容器的环节

    @Override
    protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }
                // Fetch Spring root application context and initialize the delegate early,
                // if possible. If the root application context will be started after this
                // filter proxy, we'll have to resort to lazy initialization.
                
                // 修改的第一处
//                WebApplicationContext wac = findWebApplicationContext();
//                if (wac != null) {
//                    this.delegate = initDelegate(wac);
//                }
            }
        }
    }

第二处:第一次请求的时候直接找SpringMVC的IOC容器

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // Lazily initialize the delegate if necessary.
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {

                    // 把原来的查找IOC容器的代码注释掉
                    // WebApplicationContext wac = findWebApplicationContext();


                    // 按照自己的需要重新编写
                    // 1、获取 ServletContext 对象
                    ServletContext servletContext = this.getServletContext();

                    // 2、拼接 SpringMVC将IOC容器存入ServletContext域的时候使用的属性名
                    String servletName = "springDispatcherServlet";

                    String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + servletName;

                    // 3、根据 attrName 从 ServletContext 域中获取IOC容器对象
                    WebApplicationContext wac = (WebApplicationContext) servletContext.getAttribute(attrName);
                    
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: " +
                                "no ContextLoaderListener or DispatcherServlet registered?");
                    }
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        invokeDelegate(delegateToUse, request, response, filterChain);
    }
最后修改:2021 年 07 月 21 日 08 : 41 AM
如果觉得我的文章对你有用,请随意赞赏