Spring知识补充
1.BeanFactory 和 ApplicationContext
BeanFactory
- BeanFactory是spring中最基础的接口(容器),定义了管理bean的最基础的方法
-
是spring中的原始Factory,不支持扩展AOP,Web等spring插件
-
需要获取bean对象时,才开始创建(延迟初始化)
ApplicationContext
- 继承了BeanFactory,具有它的所有功能,支持AOP、Web等插件。
- 在启动spring时就创建所有bean对象
2.Spring 中 bean的生命周期
5个大步骤
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 使用 bean
- 销毁 Destruction
细化步骤
-
对象实例化
-
属性赋值
-
初始化操作
- 若Bean实现了
BeanNameAware
接口,调用setBeanName()
,将bean的id传给setBeanName - 若Bean实现了
BeanFactoryAware
接口,调用setApplicationContext()
进行初始化 - 若Bean实现了
ApplicationContextAware
接口,调用setApplicationContext()
进行初始化 - 若Bean实现了
BeanPostProcessor
接口(AOP),调用postProcessBeforeInitialization()
进行初始化 - 若Bean实现了
InitializingBean
接口 或 bean使用<bean init-method="初始化方法">
声明了初始化方法,调用afterPropertiesSet()进行初始化 - 若Bean实现了
BeanPostProcessor
接口(AOP),调用postProcessAfterInitialization()
进行初始化
- 若Bean实现了
-
Bean创建完成放到容器中,可以使用,直到应用程序上下文被销毁
-
如果Bean实现了
DisposableBean
接口,或 配置了 ,会调用destory()方法销毁Bean对象
3.Spring 中 Bean 是否线程安全?
不一定:有状态单例Bean(存储数据的单例bean)才会存在线程安全问题
Bean的scope作用域
- singleton 单例
第一次请求被创建,所有线程共享一个实例,存在线程安全问题,是spring默认作用域 - prototype 原型
每次请求都会创建一个新对象,不是线程共享,不存在线程安全问题 - request 请求
为每个HTTP请求创建一个新的Request-Processor对象,当请求结束后,该对象实生命周期就结束 - session 会话:同一个会话共享一个实例
- global session 全局会话:所有会话共享一个实例
有状态Bean和无状态Bean
- 有状态Bean: 存储数据的Bean对象
如User,Admin的对象需要存数据,就是有状态的Bean - 无状态Bean:不存储数据,只是用它来调用一些方法
eg:Controller、Service、Dao对象都是无状态的bean,并不保存数据
有状态的单例Bean会存在线程安全问题
@Controller
@Service
@Repository
这些容器中默认是单例,如果只是调用方法,是线程安全的
如何保证 Bean 线程安全?
- 将scope设置为prototype,原型bean会在每个线程中都创建一个新的对象
- 使用ThreadLocal,为每个线程提供一份变量副本,不必去竞争同一份资源
4.循环依赖
在Java中,循环依赖是正常的,如员工关联了一个部门对象,一个部门关联了多个员工对象,在使用的时候没有问题。
但在Spring中,对象的创建需要注入依赖对象的值才可以,A对象创建需要先注入B,创建B又要先注入A,循环依赖就像一个死循环一样
循环依赖的原因是因为Bean的生命周期
在Java中初始化时,可以允许依赖的属性为null
Spring中不允许,需要为对象注入值
解决循环依赖
Spring的二级缓存可以解决循环依赖的问题
Spring的缓存
- 一级缓存 singletonObjects:保存实例化、属性注入、初始化完成的Bean
- 二级缓存 earlySingletonObjects:保存实例化完成的Bean(实例化完成,对象不为null,就可以注入,解决了循环依赖)
- 三级缓存 singletonFactories:保存Bean工厂,以便后期添加其他代理对象(如事务管理中代理对象的生成)
构造方法注入无法解决循环依赖问题
在构造方法中使用@Lazy标识依赖的属性参数,可以解决
5.过滤器与拦截器
过滤器 Filter | 拦截器 Interceptor | |
---|---|---|
实现方式 | 基于函数回调 | 基于Java反射(动态代理) |
使用范围 | servlet是tomcat等服务器实现的,需要依赖Tomcat等容器 | 是spring组件; Java程序中都可以使用 |
触发时机 | 到达servlet之前触发 | 进入servlet之后,到达Controller之前触发 |
请求范围 | 可拦截所有进入容器(Tomcat)的请求 | 只能拦截进入控制器(DispatcherServlet )的请求 |
过滤器
serlvet 过滤器
//登录验证过滤器
public class IsLoginFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//ServletRequest向下转型为,HttpServletRequest
HttpServletRequest request = (HttpServletRequest) servletRequest;
//获取session对象
HttpSession session = request.getSession();
session.setMaxInactiveInterval(10 * 60);//设置10分钟 后 session对象销毁
//从session对象中拿到User对象
SystemManager sm = (SystemManager) session.getAttribute("sm");
if (sm == null) {// sm为null 代表服务器session对象已经销毁
PrintWriter out = servletResponse.getWriter();
out.println(222);//222 : 用户对象不存在,提示重新登录
} else {//没有销毁就继续走下一个doFilter
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
/**
* 通一编码过滤器
*/
class EncodFilter implements Filter {
String str;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
str = filterConfig.getInitParameter("code");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//设置编码格式
servletRequest.setCharacterEncoding(str);
//让请求继续
filterChain.doFilter(servletRequest, servletResponse);
}
}
使用过滤器的方法
1、在web.xml中配置
<!--过滤器-->
<filter>
<filter-name>encod</filter-name>
<filter-class>com.company.dormms.filter.EncodFilter</filter-class>
<init-param>
<param-name>code</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encod</filter-name>
<url-pattern>/back/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>isLogin</filter-name>
<filter-class>com.company.dormms.filter.IsLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>isLogin</filter-name>
<url-pattern>/back/build</url-pattern>
<url-pattern>/back/buildManager</url-pattern>
</filter-mapping>
拦截器
public class LoginInterceptor implements HandlerInterceptor {
//* 当请求到达控制器之前被执行 true--继续向下执行,到达下一个拦截器,或控制器 false--不会继续向下执行*//*
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession httpSession=request.getSession();
Manage manage = (Manage) httpSession.getAttribute("manage");
if(manage !=null){
return true;
}else{
response.getWriter().println(203);
return false;
}
}
//*控制器方法执行之后执行*//*
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
//*整个请求结束后执行*//*
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
使用:
<mvc:interceptors>
<mvc:interceptor>
<!--哪些请求进入拦截器-->
<mvc:mapping path="/**"/>
<!--哪些请求不进入拦截器-->
<mvc:exclude-mapping path="/login/login"/>
<mvc:exclude-mapping path="/**/*.html"/>
<mvc:exclude-mapping path="/**/*.css"/>
<mvc:exclude-mapping path="/img/**"/>
<mvc:exclude-mapping path="/**/*.js"/>
<bean id="loginId" class="com.ffyc.team.util.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>