发表于: 2018-03-08 23:31:56
1 705
今日完成:
1. 拦截器
基本:
不依赖于Servlet容器,依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。可以使用Spring的依赖注入(DI)获取IOC容器中的各个bean,进行一些业务操作。
基于Java的反射机制,属于面向切面编程(AOP)的一种运用。
一个拦截器实例在一个controller生命周期之内可以多次调用。
只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求没办法进行拦截。
拦截器在对请求权限鉴定方面确实很有用处。
过个拦截器它们之间的执行顺序跟在SpringMVC的配置文件中定义的先后顺序有关。
常见应用场景
1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。
HandlerInterceptor拦截器接口:
package org.springframework.web.servlet;
public interface HandlerInterceptor {
boolean preHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex)
throws Exception;
}
preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器(如我们上一章的Controller实现);
返回值:true表示继续流程(如调用下一个拦截器或处理器);
false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器的afterCompletion。
HandlerInterceptorAdapter拦截器适配器
只实现需要的回调方法
运行流程:
true
false
中断流程中,比如是HandlerInterceptor2中断的流程(preHandle返回false),此处仅调用它之前拦截器的preHandle返回true的afterCompletion方法
2. 过滤器
基本:
依赖于Servlet容器,在请求进入容器之后,还未进入Servlet之前进行预处理,在请求结束返回给前段这之间进行后期处理,AOP编程思想的体现。
基于函数回调,几乎可以对所有请求进行过滤
一个过滤器实例只能在容器初始化时调用一次???????
使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
过滤器的运行是依赖于servlet容器的,跟springmvc等框架并没有关系。并且,多个过滤器的执行顺序跟xml文件中定义的先后关系有关
过滤器Filter生命周期:init()->doFilter()->destroy(),由部署文件中的filter元素驱动。
使用:
Spring的web包中中提供有很多过滤器,这些过滤器位于org.springframework.web.filter并且理所当然地实现了javax.servlet.Filter
实现方式:
(1) 直接实现Filter,这一类过滤器只有CompositeFilter;
(2) 继承抽象类GenericFilterBean,该类实现了javax.servlet.Filter,这一类的过滤器只有一个,即DelegatingFilterProxy;
(3) 继承抽象类OncePerRequestFilter,该类为GenericFilterBean的直接子类,这一类过滤器包括CharacterEncodingFilter、HiddenHttpMethodFilter、HttpPutFormContentFilter、RequestContextFilter和ShallowEtagHeaderFilter;
(4) 继承抽象类AbstractRequestLoggingFilter,该类为OncePerRequestFilter的直接子类,这一类过滤器包括CommonsRequestLoggingFilter、Log4jNestedDiagnosticContextFilter和ServletContextRequestLoggingFilter。
genericFilterBean是任何类型的过滤器的一个比较方便的超类,这个类主要实现的就是从web.xml文件中取得init-param中设定的值,然后对Filter进行初始化(当然,其子类可以覆盖init方法)。
OncePerRequestFilter继承自GenericFilterBean,那么它自然知道怎么去获取配置文件中的属性及其值,所以其重点不在于取值,而在于确保在接收到一个request后,每个filter只执行一次,它的子类只需要关注Filter的具体实现即doFilterInternal。
AbstractRequestLoggingFilter是对OncePerRequestFilter的扩展,它除了遗传了其父类及祖先类的所有功能外,还在doFilterInternal中决定了在过滤之前和之后执行的事件,它的子类关注的是beforeRequest和afterRequest。
web.xml位置:
过滤器放在web资源之前,可以在请求抵达它所应用的web资源(可以是一个Servlet、一个Jsp页面,甚至是一个HTML页面)之前截获进入的请求,并且在它返回到客户之前截获输出请求。
Filter链,在web.xml中哪个先配置,哪个就先调用。在filter中也可以配置一些初始化参数。
处理:
Java中的Filter 并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。 主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链。
种类:
l 用户授权的Filter: Filter 负责检查用户请求,根据请求过滤用户非法请求。
l 日志Filter: 详细记录某些特殊的用户请求。
l 负责解码的Filter: 包括对非标准编码的请求解码。
l 能改变XML 内容的XSLTFilter 等。
作用:
l 在HttpServletRequest 到达Servlet 之前,拦截客户的HttpServletRequest 。
l 根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
l 在HttpServletResponse 到达客户端之前,拦截HttpServletResponse 。
l 根据需要检查HttpServletResponse ,可以修改HttpServletResponse 头和数据。
实现Filter
(1) 创建Filter 处理类
创建Filter 必须实现javax.servlet.Filter 接口,在该接口中定义了三个方法。
• void init(FilterConfig config): 用于完成Filter 的初始化。
• void destroy(): 用于Filter 销毁前,完成某些资源的回收。
• void doFilter(ServletRequest request, ServletResponse response,FilterChain chain): 实现过滤功能,该方法就是对每个请求及响应增加的额外处理。
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)){
request.setCharacterEncoding(this.encoding);
if (this.forceEncoding) {
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
}
(2) 在web.xml 文件中配置Filter
<!-- 编码处理过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<!-- 过滤器Filter类 -->
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 注入的set参数 -->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<!-- 过滤的url类型 -->
<url-pattern>*.do</url-pattern>
</filter-mapping>
3. 监听器
Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。
主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。
<!-- ContextLoaderListener监听器启动Web容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- log4j配置文件位置 -->
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/config/log4.properties</param-value>
</context-param>
<!-- 利用spring来使用log4j -->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
自定义:
要想顺利的创建监听器,并起作用,这个过程中需要这样几个角色:
1、 事件(event)可以封装和传递监听器中要处理的参数,如对象或字符串,并作为监听器中监听的目标。
定义一个事件(MyTestEvent ),需要继承spring的ApplicationEvent
import org.springframework.context.ApplicationEvent;
public class MyTestEvent extends ApplicationEvent{
/**
*
*/
private static final long serialVersionUID = 1L;
private String msg ;
public MyTestEvent(Object source,String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
2、 监听器(listener)具体根据事件发生的业务处理模块,这里可以接收处理事件中封装的对象或字符串。
定义一下监听器,自己定义的监听器需要实现ApplicationListener,同时泛型参数要加上自己要监听的事件Class名,在重写的方法onApplicationEvent中,添加自己的业务处理
非注解:
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import com.mu.event.MyTestEvent;
@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent>{//泛型
@Override
public void onApplicationEvent(MyTestEvent event) {
System.out.println("非注解监听器:" + event.getMsg());
}
}
注解:
注解形式的监听器的执行在非注解的前面。
好处:不用每次都去实现ApplicationListener,可以在一个class中定义多个方法,用@EventListener来做方法级别的注解。
@Component
public class MyAnnotationListener {
@EventListener
public void listener1(MyTestEvent event) {
System.out.println("注解监听器1:" + event.getMsg());
}
}
3、事件发布者(publisher)事件发生的触发者。
注入一下spring的ApplicationContext,通过spring的应用上下文进行事件的发布,参数就是要发布事件的MyTestEvent事件对象
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class MyTestEventPubLisher {
@Autowired
private ApplicationContext applicationContext;
// 事件发布方法
public void pushListener(String msg) {
applicationContext.publishEvent(new MyTestEvent(this, msg));
}
}
写一个测试Controller,模拟调用一下事件发布操作
@Controller
public class TestEventListenerController {
@Autowired
private MyTestEventPubLisher publisher;
@RequestMapping(value = "/test/testPublishEvent1" )
public void testPublishEvent(){
publisher.pushListener("我来了!");
}
}
在实际工作中,事件监听经常会用在发送通知,消息、邮件等情况下,那么这个时候往往是需要异步执行的,不能在业务的主线程里面,那怎么样可以实现异步处理呢?
1) 可以写一个线程,单独做这个事情
2) 用spring的@Async注解方式,一个简单的注解,就可以把某一个方法或者类下面的所有方法全部变成异步处理的方法,这样,就可以做到处理监听事件的时候也不会阻塞主进程了
@Component
public class MyAnnotationListener {
@EventListener
public void listener1(MyTestEvent event) {
System.out.println("注解监听器1:" + event.getMsg());
}
@EventListener
@Async
public void listener2(MyTestEvent event) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("注解监听器2:" + event.getMsg());
}
}
相关配置:
注解的应用范围:
类:表示这个类中的所有方法都是异步的
方法:表示这个方法是异步的,如果类也注解了,则以这个方法的注解为准
配置:executor:指定一个缺省的executor给@Async使用。
配置参数:
id:当配置多个executor时,被@Async(“id”)指定使用;也被作为线程名的前缀。
pool-size:
core size:最小的线程数,缺省:1
max size:最大的线程数,缺省:Integer.MAX_VALUE
queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size,这时系统会拒绝这个任务并抛出TaskRejectedException异常(缺省配置的情况下,可以通过rejection-policy来决定如何处理这种情况)。缺省值为:Integer.MAX_VALUE
keep-alive:超过core size的那些线程,任务完成后,再经过这个时长(秒)会被结束掉
rejection-policy:当pool已经达到max size的时候,如何处理新任务
ABORT(缺省):抛出TaskRejectedException异常,然后不执行
DISCARD:不执行,也不抛出异常
DISCARD_OLDEST:丢弃queue中最旧的那个任务
CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
1、如果是spring配置文件方式,可以在spring主配置中添加:
<task:annotation-driven executor="asyncExecutor" />
<task:executor id="asyncExecutor" pool-size="100-10000" queue-capacity="10"/>
3、 如果是无xml的java配置方式的话,需要在配置类上添加@EnableAsync注解来开启异步注解。
4. 接口的幂等性
幂等性是系统的接口对外一种承诺(而不是实现), 承诺只要调用接口成功, 外部多次调用对系统的影响是一致的. 声明为幂等的接口会认为外部调用失败是常态, 并且失败之后必然会有重试. 幂等接口的内部实现需要有对内保护机制
5. 拦截器线程安全
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)。
private NamedThreadLocal<Long> startTimeThreadLocal =new NamedThreadLocal<Long>("StopWatch-StartTime");
startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量
NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。
明日计划:
1. Spring AOP, DES加密
2. 查看压测的相关知识
3. 下载JMeter,压测自己的JSP,查看TPS数据,调整并发数,压到程序挂掉为止。
4. 加上Memcache,在新建数据的时候同时维护好缓存(没有新建数据接口就自己加上,可以分成是压测JSP和Json接口两种方式) ,确定数据没问题,重新压测服务器,测出90%的线在哪里.
遇到的问题:
1. 拦截器和监听器之间该如何做选择,对于登陆验证来说
收获:
1. 熟悉拦截器的实现并阅读部分源码,对实现机制有一定了解
2. 了解了过滤器和监听器的作用和实现方式
评论