在看到攔截器的時候,大家一定會想到另外一個詞,就是過濾器。兩者到底有什麼區別呢?過濾器,從字面的意思理解就是過濾用的,當很多請求過來的時候,我們對其進行過濾,滿足一定條件的時候,才放行。在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標籤,來進行具體的配置。