在看到拦截器的时候,大家一定会想到另外一个词,就是过滤器。两者到底有什么区别呢?过滤器,从字面的意思理解就是过滤用的,当很多请求过来的时候,我们对其进行过滤,满足一定条件的时候,才放行。在Java中,过滤器是使用Filter实现的,实现原理都是基于回调函数的。最常见的过滤器的应用就是字符编码的过滤、用户信息验证的过滤等。拦截器呢,就是用来拦截的,可以在方法的执行时,添加一些其他的信息,拦截器是使用Interceptor实现的,实现原理是基于Java的反射机制的。最常见的拦截器的应用有:添加访问日志、性能监控等。今天我们就来看看怎么用Spring的拦截器为方法添加访问日志。
首先,我们创建一个拦截器类,由于我们需要拦截的是方法,所以,就继承MethodInterceptor类。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 监控日志拦截器
* @author lizhiyang
*
*/
public class MonitorLogInterceptor implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(MonitorLogInterceptor.class);
public Object invoke(MethodInvocation methodinvocation) throws Throwable {
Method method = methodinvocation.getMethod();
//方法执行前输出
logger.info("methodIn:methodName="+method.getName());
try {
//执行方法
return methodinvocation.proceed();
} finally {
//方法执行后输出
logger.info("methodOut:methodName="+method.getName());
}
}
}
在方法的执行前和执行后都加上日志输出。
接着,有的时候,我们可能需要自定义多个拦截器,这个时候,我们需要有一个拦截器链,把这些拦截器都串起来。
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 拦截器链
* @author lizhiyang
*
*/
public class InterceptorChain implements MethodInterceptor {
private List<MethodInterceptor> chains;
public Object invoke(MethodInvocation methodinvocation) throws Throwable {
InterceptorChainSupport support = new InterceptorChainSupport(methodinvocation, new ArrayList<MethodInterceptor>(chains));
return support.proceed();
}
public List<MethodInterceptor> getChains() {
return chains;
}
public void setChains(List<MethodInterceptor> chains) {
this.chains = chains;
}
}
里面我们用到了一个类:InterceptorChainSupport,他的实现如下:
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class InterceptorChainSupport implements MethodInvocation {
private MethodInvocation proxy;
private List<MethodInterceptor> chains;
public InterceptorChainSupport(MethodInvocation proxy,List<MethodInterceptor> chains) {
this.proxy = proxy;
this.chains = chains;
}
public MethodInvocation getProxy() {
return proxy;
}
public void setProxy(MethodInvocation proxy) {
this.proxy = proxy;
}
public List<MethodInterceptor> getChains() {
return chains;
}
public void setChains(List<MethodInterceptor> chains) {
this.chains = chains;
}
public Object[] getArguments() {
return proxy.getArguments();
}
public AccessibleObject getStaticPart() {
return proxy.getStaticPart();
}
public Object getThis() {
return proxy.getThis();
}
public Object proceed() throws Throwable {
//如果拦截器链不空,则继续执行拦截器
if(chains != null && chains.size() > 0) {
//递归调用,一直调用到拦截器链的最后一个
return (chains.remove(0)).invoke(this);
} else {
return proxy.proceed();
}
}
public Method getMethod() {
return proxy.getMethod();
}
}
InterceptorChainSupport是真正来处理拦截器链的,遍历执行所有的拦截器。在InterceptorChain中构造InterceptorChainSupport的时候要特别注意,一定要new一个新的List来存放chains,否则,会造成调用链只能执行一次的情况。
此处的执行过程是这样的:当调用相应的方法时,调用InterceptorChain.invoke()----->InterceptorChainSupport.proceed()---->***Interceptor.invoke()------>InterceptorChainSupport().proceed()------......---->真正的方法处理---->方法之后的拦截处理。
<!-- 配置日志监控拦截器 -->
<bean id="monitorLogInterceptor" class="com.demo.interceptor.MonitorLogInterceptor" />
<!-- 配置拦截器链,保存所有的拦截器 -->
<bean id="interceptorChain" class="com.demo.interceptor.InterceptorChain">
<property name="chains">
<list>
<ref bean="monitorLogInterceptor"/>
</list>
</property>
</bean>
<!-- 配置拦截器和需要拦截的bean -->
<bean id="serviceProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>interceptorChain</value>
</list>
</property>
<property name="beanNames">
<value>*Service</value>
</property>
</bean>
这样配置之后,在spring容器加载的时候,spring就知道了执行*Service类中的方法时候,需要应用interceptorChain中的拦截器。
这个时候呢,就有了一个问题,如果我不想给这个类的每个方法都进行拦截,只拦截一部分呢?这个时候我们可以借助注解来实现。为需要拦截的方法上加上注解。
首先我们创建一个注解类,MonitorLog。
/**
* 1.RetentionPolicy.SOURCE ——只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS ——编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
*
*/
@Retention(RetentionPolicy.RUNTIME)
/**
* 该注解应用于方法
*/
@Target(ElementType.METHOD)
/**
* 指明被注解的类会自动继承,如果我们把注解放在接口的方法上,那么实现该接口的类也会被继承该注解
*/
@Inherited
/**
* Documented 注解表明这个注解应该被 javadoc工具记录.
*/
@Documented
public @interface MonitorLog {
}
然后我们在需要拦截的方法上天剑@MonitorLog注解。
public interface UserService {
@MonitorLog
public boolean insertUser(UserModel user);
public UserModel getUser(int userId);
public String test();
public void user(String name);
}
我们现在还需要修改MonitorLogInterceptor类,只有添加@MonitorLog的方法才进行拦截,其他的不拦截。
public class MonitorLogInterceptor implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(MonitorLogInterceptor.class);
public Object invoke(MethodInvocation methodinvocation) throws Throwable {
Method method = methodinvocation.getMethod();
//获取方法的MonitorLog注解
MonitorLog log = method.getAnnotation(MonitorLog.class);
boolean bLog = false;
//该方法存在MonitorLog注解,则输出日志
if(log != null) {
bLog = true;
//方法执行前输出
logger.info("methodIn:methodName="+method.getName());
}
try {
//执行方法
return methodinvocation.proceed();
} finally {
if(bLog) {
//方法执行后输出
logger.info("methodOut:methodName="+method.getName());
}
}
}
}
以上,就是spring拦截器的一个简单的应用。当然了,我们也可以使用spring的aop标签,来进行具体的配置。