作为独立的 REST 应用服务器,需要统计每个接口被调用的次数,消耗时间,成功与失败结果等数据,用第三方的统计工具无法满足数据和性能,所以用 cxf 的拦截器实现了一个。
进入InInterceptor:
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.springframework.stereotype.Component;
/**
* 性能分析拦截器
*
* @author michael
*
*/
@Component
public class ProfilingInInterceptor extends AbstractPhaseInterceptor<Message> {
public ProfilingInInterceptor() {
super(Phase.RECEIVE);
}
public void handleMessage(Message message) throws Fault {
message.getExchange().put("TRACKER_START_TIME", System.currentTimeMillis());
}
}
输出:OutInterceptor
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.plusrun.rest.ResultBase;
import com.plusrun.profiling.service.impl.ProfilerManagerImpl;
/**
* 性能分析拦截器
*
* @author michael
*
*/
@Component
public class ProfilingOutInterceptor extends AbstractPhaseInterceptor<Message> {
@Autowired
@Qualifier("profilerManager")
ProfilerManagerImpl profilerManager;
public ProfilingOutInterceptor() {
super(Phase.SEND);
}
public void handleMessage(Message message) throws Fault {
Object sTime = message.getExchange().get("TRACKER_START_TIME");
long startTime = 0;
if (sTime != null && (Long) sTime >= 0) {
startTime = (Long) sTime;
}
// String method
OperationResourceInfo ori = message.getExchange().get(OperationResourceInfo.class);
if (ori != null) {
Method m = ori.getMethodToInvoke();
String httpStatus = message.get("org.apache.cxf.message.Message.RESPONSE_CODE").toString();
String serviceName = m.toString().substring(m.toString().lastIndexOf(" "));
String methodName = m.getName();
String httpMethod = ori.getHttpMethod();
String path = ori.getClassResourceInfo().getPath().value();
int returnValue = 0;
Object clazz = m.getReturnType();
if (clazz != null && clazz instanceof ResultBase) {
returnValue = ((ResultBase) clazz).getRet();
}
for (Annotation an : m.getDeclaredAnnotations()) {
String fullPath = an.toString();
if (fullPath.startsWith("@javax.ws.rs.Path")) {
path = path + fullPath.substring(fullPath.indexOf("=")+1, fullPath.length() - 1);
break;
}
}
profilerManager.access(path, serviceName, methodName, httpMethod, httpStatus, returnValue, System.currentTimeMillis() - startTime);
}
}
}
性能服务:ProfilerManagerImpl
import org.springframework.stereotype.Service;
@Service("profilerManager")
public class ProfilerManagerImpl {
AccessMBean accessBean =new AccessMBean();
public void access(String path, String service, String method, String httpMethod, String httpStatus, int logicReturn, long elapsedTime) {
accessBean.addAccess(path, service, method, httpMethod, httpStatus, logicReturn, elapsedTime);
}
public AccessMBean getAccessStatus(){
return accessBean;
}
public void cleanAccessStatus(){
accessBean =new AccessMBean();
}
}
访问MBean:AccessMBean
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.NONE)
@XmlType(namespace = "com.plusrun.profiling", name = "AccessMBean")
@XmlRootElement(namespace = "com.plusrun.profiling")
public class AccessMBean {
private long elapsedTime = 0;
private long accessCount = 0;
private long httpErrorCount = 0;
private long logicFaultCount = 0;
private Date startDate = new Date();
private Map<String, AccessAccumulator> accumulatorMap = new HashMap<String, AccessAccumulator>();
public AccessMBean() {
}
public void addAccess(String path, String service, String method, String httpMethod, String httpStatus, int logicReturn, long elapsedTime) {
boolean err = false;
if (httpStatus.indexOf("200") < 0) {
err = true;
}
AccessAccumulator aa = accumulatorMap.get(httpMethod + "-" + path);
if (aa == null) {
aa = new AccessAccumulator(path, httpMethod, service, method);
accumulatorMap.put(httpMethod + "-" + path, aa);
}
aa.addOperation(elapsedTime, err, logicReturn);
if (err) {
this.httpErrorCount++;
}
if (logicReturn != 0) {
this.logicFaultCount++;
}
this.accessCount++;
this.elapsedTime += elapsedTime;
}
@XmlElement
public float getTps() {
float tps = 0;
if (elapsedTime > 0 && accessCount > 0){
tps = ((float) accessCount) / ((float) elapsedTime / 1000);
tps = (float)(Math.round(tps*100))/100;
}
return tps;
}
@XmlElement
public float getErrRat() {
float rat = 0;
if (accessCount > 0 && httpErrorCount > 0){
rat = (float) httpErrorCount / (float) accessCount;
rat = (float)(Math.round(rat*100))/100;
}
return rat;
}
@XmlElement
public float getFaultRat() {
float rat = 0;
if (logicFaultCount > 0 && accessCount > 0){
rat = (float) logicFaultCount / (float) accessCount;
rat = (float)(Math.round(rat*100))/100;
}
return rat;
}
@XmlElement
public long getElapsedTime() {
return elapsedTime;
}
@XmlElement
public long getAccessCount() {
return accessCount;
}
@XmlElement
public long getHttpFaultCount() {
return httpErrorCount;
}
@XmlElement
public long getLogicFaultCount() {
return logicFaultCount;
}
@XmlElement
public Date getStartTime() {
return startDate;
}
@XmlElement
public Date getSystemTime() {
return new Date();
}
@XmlElement
public long getTotalAccess() {
return this.getAccessCount();
}
@XmlElement
public long getTotalTimeMS() {
return getSystemTime().getTime()-getStartTime().getTime();
}
@XmlElement
public float getTotalTps() {
float tps = 0;
if (getTotalTimeMS() > 0 && this.getAccessCount() > 0){
tps = ((float) this.getAccessCount()) / ((float) getTotalTimeMS() / 1000);
tps = (float)(Math.round(tps*100))/100;
}
return tps;
}
@XmlElement
public Collection<AccessAccumulator> getTracking() {
return accumulatorMap.values();
}
}
访问累加:AccessAccumulator
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.NONE)
@XmlType(namespace = "com.plusrun.profiling", name = "AccessAccumulator")
@XmlRootElement(namespace = "com.plusrun.profiling")
public class AccessAccumulator {
@XmlElement
public String getPath() {
return path;
}
@XmlElement
public String getHttpMethod() {
return httpMethod;
}
@XmlElement
public String getService() {
return service;
}
@XmlElement
public String getMethod() {
return method;
}
@XmlElement
public long getCount() {
return count;
}
@XmlElement
public long getTotal() {
return total;
}
@XmlElement
public long getMax() {
return max;
}
@XmlElement
public long getMin() {
return min;
}
@XmlElement
public float getAvg() {
float avg = 0;
if (total > 0 && count > 0){
avg = (float) total / (float) count;
avg = (float)(Math.round(avg*100))/100;
}
return avg;
}
@XmlElement
public float getTps() {
float tps = 0;
if (total > 0 && count > 0){
tps = ((float) count) / ((float) total / 1000);
tps = (float)(Math.round(tps*100))/100;
}
return tps;
}
@XmlElement
public long getErr() {
return err;
}
@XmlElement
public long getFault() {
return fault;
}
@XmlElement
public float getErrRat() {
float rat = 0;
if (count > 0 && err > 0){
rat = (float) err / (float) count;
rat = (float)(Math.round(rat*100))/100;
}
return rat;
}
@XmlElement
public float getFaultRat() {
float rat = 0;
if (fault > 0 && count > 0){
rat = (float) fault / (float) count;
rat = (float)(Math.round(rat*100))/100;
}
return rat;
}
String path;
String httpMethod;
String service;
String method;
private long count = 0;
private long total = 0;
private long max = 0;
private long min = -1;
/**
* http error
*/
private long err = 0;
/**
* logic error
*/
private long fault = 0;
public AccessAccumulator() {
}
public AccessAccumulator(String path, String httpMethod, String service, String method) {
this.path = path;
this.httpMethod = httpMethod;
this.service = service;
this.method = method;
}
@XmlElement
public String getAccessPath() {
return httpMethod + "-" + path;
}
public void addOperation(long timeMS, boolean httpErr, int ret) {
this.count++;
this.total += timeMS;
if (httpErr) {
err++;
}
if (ret != 0) {
fault++;
}
if (timeMS>max){
max =timeMS;
}
if (timeMS<min ||min==-1){
min =timeMS;
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{httpMethod").append(":").append(getHttpMethod()).append(",");
sb.append("path").append(":").append(getPath()).append(",");
sb.append("service").append(":").append(getService()).append(",");
sb.append("method").append(":").append(getMethod()).append(",");
sb.append("count").append(":").append(getCount()).append(",");
sb.append("total").append(":").append(getTotal()).append(",");
sb.append("max").append(":").append(getMax()).append(",");
sb.append("min").append(":").append(getMin()).append(",");
sb.append("avg").append(":").append(getAvg()).append(",");
sb.append("tps").append(":").append(getTps()).append(",");
sb.append("err").append(":").append(getErr()).append(",");
sb.append("errRat").append(":").append(getErrRat()).append(",");
sb.append("fault").append(":").append(getFault()).append(",");
sb.append("faultRat").append(":").append(getFaultRat()).append("}");
return sb.toString();
}
}
Rest Service: ProfilingServiceResource
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author michael
*
*/
@Path("/profiling")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Service
public class ProfilingServiceResource {
@Autowired
ProfilerManagerImpl profilerManager;
@GET
@Path("/state")
public AccessMBean getAccessStatus() {
return profilerManager.getAccessStatus();
}
@POST
@Path("/clean")
public AccessMBean cleanAccessStatus() {
profilerManager.cleanAccessStatus();
return profilerManager.getAccessStatus();
}
}
返回值Bean:
public class ResultBase {
private Integer errcode;
private Integer ret;
private String msg;
public Integer getErrcode() {
return errcode;
}
public void setErrcode(Integer errcode) {
this.errcode = errcode;
}
public Integer getRet() {
return ret;
}
public void setRet(Integer ret) {
this.ret = ret;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
CXF REST 配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:util="http://www.springframework.org/schema/util" xmlns:cxf="http://cxf.apache.org/core" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <bean id="cxf" class="org.apache.cxf.bus.spring.SpringBus"> <property name="inInterceptors"> <list> <ref bean="profilingInInterceptor"/> </list> </property> <property name="outInterceptors"> <list> <ref bean="profilingOutInterceptor"/> </list> </property> </bean> <util:list id="jsonKeys"> </util:list> <util:list id="jsonTypes"> <value>application/json</value> <value>application/jettison</value> </util:list> <bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.json.JSONProvider"> <property name="serializeAsArray" value="true" /> <property name="arrayKeys" ref="jsonKeys" /> <property name="produceMediaTypes" ref="jsonTypes" /> <property name="consumeMediaTypes" ref="jsonTypes" /> <property name="ignoreNamespaces" value="true" /> <property name="dropRootElement" value="true" /> <property name="ignoreMixedContent" value="true" /> <property name="attributesToElements" value="true" /> </bean> <jaxrs:server id="restApiResource" address="/"> <jaxrs:serviceBeans> <ref bean="profilingServiceResource" /> </jaxrs:serviceBeans> <jaxrs:providers> <ref bean="jsonProvider" /> </jaxrs:providers> <jaxrs:extensionMappings> <entry key="json" value="application/json" /> <entry key="xml" value="application/xml" /> </jaxrs:extensionMappings> </jaxrs:server> </beans>