Web项目加入SpringSecurity后抛异常NoSuchBeanDefinitionException
一、先让我们来看看异常信息
二、在解决异常前我们先需要知道的事情
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 生效
②SpringMVC的IOC容器
SpringMVC的IOC容器中装配的 Bean 有 Controller,所以当 SpringSecurity 加载到SpringMVC的IOC容器时就可以对 Controller 请求生效。(这也正是我们想要的效果,所以该web项目采用的SpringMVC的IOC容器)
3、DelegatingFilterProxy初始化后查找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);
}