线程本地数据ThreadLocal


layout: post title: "线程本地数据ThreadLocal" subtitle: " "每个线程都有自己的数据,互不干扰。"" date: 2018-10-07 09:00:00 author: "青乡" header-img: "img/post-bg-2015.jpg" catalog: true tags: - multi thread

是什么

顾名思义,线程本地类。

作用

在多线程环境下,每个线程都有一个副本。

如果是普通数据,那么所有线程操作的都是同一份数据,会出现线程同步的问题。

所以,每个线程都有一个副本的好处就是避免线程同步的问题。

应用场景

每个线程都有自己的数据,而不是多线程之间共享这个数据。

1.userId
2.transactionId
3.threadId

具体怎么使用

//jdk7-API官方文档
import java.util.concurrent.atomic.AtomicInteger;

//获取线程id:不是系统默认的线程id,是自定义线程id
 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0); //初始值是0

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() { //匿名对象
             @Override protected Integer initialValue() {
                 return nextId.getAndIncrement(); //第一个线程调用的时候,得到的值是1;第二个线程调用的时候,得到的值是2;后面以此类推
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() { //获取当前线程的线程id
         return threadId.get(); //每个线程第一次调用ThreadLocal.get()方法时,因为没有初始值,所以都会调用initialValue()方法,得到一个初始值
     }
 }
复制代码

总结
1.类
自定义工具类

2.类包含了线程本地数据
确保每个线程都有自己的副本数据。//数据属于每个线程自己

3.每个线程获取数据的时候,是通过线程安全的方法去获取数据的 比如,使用并发包-原子类。这样就确保了获取的数据是唯一的。//数据唯一性

底层实现

第一次获取值
第一次调用get()方法获取值的时候,会调用initialValue()方法。所以,一般需要重写initialValue()方法。

//jdk7-ThreadLocal
/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) { //第一次调用get(),map为null
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue(); //第一次调用get(),会调用初始化方法initialValue()得到一个初始值
    }
    
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue(); //调用初始化方法,得到初始值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) 
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the <tt>initialValue</tt> method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns <tt>null</tt>; if the
     * programmer desires thread-local variables to have an initial
     * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null; //默认的初始化方法,值是null。所以需要重写。
    }
复制代码

数据保存在哪里?
有一个数据保存每个线程自己的数据。这个数据使用的数据结构是映射map,jdk自定义了一个新的映射map-ThreadLocalMap。

这个map与其他jdk-map类不同的地方在于,
1.在哪里?
这个map类是在ThreadLocal里面,也就是说,这个数据是与每个线程紧密相关的,只是供内部使用。
2.key?
基于1的原因,导致ThreadLocalMap实现的时候,它的key是什么呢?是线程本身吗?或者换个问法:

为什么不用个全局的map来实现threadlocal,key为当前threalocal+thread,value为其对应的值。毕竟这个实现简单又易于理解。

这里涉及到对象回收的问题。

所以,jdk使用的是哪一种解决方案呢?
1》Thread.map map就是ThreadLocalMap。我们说,每个线程都有自己的数据,为什么这么说呢?原因就在于每个线程都有一个自己的数据ThreadLocalMap。ThreadLocalMap存放了真正的数据value。

2》map(ThreadLocal,value) key是ThreadLocal,而ThreadLocal是final,系统全局唯一,即所有线程使用的是同一个key。同一个key,那怎么找到对应线程自己的数据?虽然key是一样的,但是map不一样,这里说的不一样,是说每个线程都有自己的map,每个线程取数据的时候,也是分2步:1.先取自己线程的map 2.再取map的value。所以哪怕key一样,也没有关系。

上面讲的是只有一个线程本地数据的情况下,key其实是一样的,因为是同一个线程本地数据。所有的线程使用的也是同一个线程本地数据。

那么能不能定义多个线程本地数据?可以。如果定义了多个线程本地数据,key又是什么样的?每个线程本地数据都有不同的key,因为现在是多个线程本地数据,所有的线程都可以访问多个线程本地数据,获取的时候,获取到的就是不同的线程本地数据,因为写的时候是根据当前ThreadLocal.set(this,value)写进去的,所以ThreadLocal.get()的时候获取到的就是当前ThreadLocal。

/**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value; //值

            Entry(ThreadLocal k, Object v) { //键是线程本身ThreadLocal
                super(k);
                value = v;
            }
        }
        
    
    /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal key, Object value) { //写方法

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    
    /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal key) { //读方法
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
复制代码

工作应用

支付

所有的服务/业务,只有1个地方使用到了线程本地数据。

分布式事务
1.分布式事务
2.事务id //线程本地数据


为什么要使用线程本地数据? 这个数据是分布式事务的流水id,每个用户的每个请求都是一个单独的线程,所以应该作为线程本地数据。


代码

//定义:类-静态数据(线程安全数据)



package com.gzh.dpp.dts.manage;



public class BusinessIDManager {

	

	private static ThreadLocal<String> businessID = new ThreadLocal<String>(); //线程安全数据 //这个数据为什么要线程安全?这个数据是分布式事务的流水id,每个用户的每个请求都是一个单独的线程,所以应该作为线程本地数据。

	

	public static void setBusinessId(String id) {

		businessID.set(id);

	}

	

	public static String getBusinessId() {

		return businessID.get();

	}

	

	public static void remove() {

		businessID.remove();

	}



}


复制代码

//写数据

public String beginTransaction(MainBusinessActivity businessActivity) {		
		try {
			String transactionId = businessService.createBusinessActivity(businessActivity);
			BusinessIDManager.setBusinessId(businessActivity.getTransactionId()); //写数据
			
			return transactionId;
		} catch(Exception ex) {
			log.error("DTS:begin business transaction exception.", ex);
			return null;
		}		
	}
复制代码

//读数据

/**
	 * 重新提交业务活动
	 * @param transactionId
	 * @return
	 */
	public boolean reconfirmTransaction(String transactionId) {
		log.info("DTS:reconfirm transaction:"+transactionId);
		
		try {
			MainBusinessActivity activity = businessService.getBusinessActivity(transactionId, true);
			log.info(activity.toString());
			
			if(!HandleFailureMode.RECONFIRM.getMode().equals(activity.getHandleFailure())) {
				log.warn("Handle mode of business failured is not reconfirm,ID is "+transactionId);
				return false;
			}
			
			//重新提交分支业务
			List<SubBusinessAction> reconfirmList = new ArrayList<SubBusinessAction> ();
			for(SubBusinessAction subAction : activity.getActionList()) {
				//查询执行结果
				String serviceName = subAction.getServiceName();
				Object serviceObj = AppContext.getBean(serviceName);
				CurrentSubAction currentAction = (CurrentSubAction)MethodUtils.invokeMethod(serviceObj, "getCurrentAction", new String[] {transactionId, serviceName});
				if(currentAction == null) {
					reconfirmList.add(subAction);
				}					
			}
			//没有执行成功的分支服务
			if(activity.getActionList().size() == reconfirmList.size()) {
				endTransaction(transactionId);
				return false;
			}
			
			for(SubBusinessAction subAction : reconfirmList) {
				String serviceName = subAction.getServiceName();
				Object serviceObj = AppContext.getBean(serviceName);
				
				String reconfirmMethodName = subAction.getMethodName();
				ISerializer serializer = SerializerFactory.getSerializer();
				
				String serializerStr = subAction.getParamsValue();				
				Object paramsValue = serializer.mergeFrom(serializerStr);
				
				if(BusinessIDManager.getBusinessId() == null) {
					BusinessIDManager.setBusinessId(transactionId);
				} else if(!transactionId.equals(BusinessIDManager.getBusinessId())) {
					log.warn("transactionId:"+transactionId + " ThreadLocal:"+BusinessIDManager.getBusinessId());
					BusinessIDManager.setBusinessId(transactionId);
				}
				
				Object resultObj = MethodUtils.invokeMethod(serviceObj, reconfirmMethodName, paramsValue);
				log.info(new StringBuilder("reconfirm transaction[id=").append(transactionId).append(",serviceName=").append(serviceName)
						.append("] execute result is").append(resultObj).toString());
			}			
			
		} catch(Exception ex) {
			log.error("DTS:reconfirm transaction["+transactionId+"] exception.", ex);			
			
			return false;
		} finally {
			BusinessIDManager.remove();
		}
		
		//重新提交成功,结束业务活动
		endTransaction(transactionId);		
		
		return true;
	}
复制代码

注:写数据和读数据的完整代码。



package com.gzh.dpp.dts.manage;



import java.util.ArrayList;

import java.util.List;



import org.apache.commons.lang.reflect.MethodUtils;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;



import com.gzh.commons.serializer.ISerializer;

import com.gzh.commons.serializer.SerializerFactory;

import com.gzh.dpp.dts.enums.ActionStatus;

import com.gzh.dpp.dts.enums.ActivityStatus;

import com.gzh.dpp.dts.enums.HandleFailureMode;

import com.gzh.dpp.dts.model.CurrentSubAction;

import com.gzh.dpp.dts.model.MainBusinessActivity;

import com.gzh.dpp.dts.model.SubBusinessAction;

import com.gzh.dpp.dts.service.IBusinessService;

import com.gzh.commons.context.AppContext;



public class TransactionManager implements ITransactionManager, IExceptionTransactionService {

	private static Log log = LogFactory.getLog(TransactionManager.class);

	

	private IBusinessService businessService;

	public void setBusinessService(IBusinessService businessService) {

		this.businessService = businessService;

	}	

	

	public String beginTransaction(MainBusinessActivity businessActivity) {		

		try {

			String transactionId = businessService.createBusinessActivity(businessActivity);

			BusinessIDManager.setBusinessId(businessActivity.getTransactionId());

			

			return transactionId;

		} catch(Exception ex) {

			log.error("DTS:begin business transaction exception.", ex);

			return null;

		}		

	}

	

	public boolean endTransaction(String transactionId) {

		BusinessIDManager.remove();	

		

		try {

			return businessService.updateBusinessActivityEnd(transactionId);

		} catch(Exception ex) {

			log.error("DTS:end business transaction exception.", ex);

			return false;

		}		

	}

	

	/**

	 * 取消回滚业务活动

	 * @param transactionId  业务活动唯一标识

	 * @return

	 */

	public boolean rollBackTransaction(String transactionId) {

		log.info("DTS:rollback transaction:"+transactionId);

		

		BusinessIDManager.remove();		

		

		try {

			MainBusinessActivity activity = businessService.getBusinessActivity(transactionId, true);

			log.info(activity.toString());

			

			if(!HandleFailureMode.CANCEL.getMode().equals(activity.getHandleFailure())) {

				log.warn("Handle mode of business failured is not cancel,ID is "+transactionId);

				return false;

			}

			

			//失败回滚分支业务

			for(SubBusinessAction subAction : activity.getActionList()) {

				//查询执行结果

				String serviceName = subAction.getServiceName();

				Object serviceObj = AppContext.getBean(serviceName);

				CurrentSubAction currentAction = (CurrentSubAction)MethodUtils.invokeMethod(serviceObj, "getCurrentAction", new String[] {transactionId, serviceName});

				if(currentAction != null && currentAction.getActionStatus().equals(ActionStatus.CONFIRM.getStatus())) {

					String cancelMethodName = subAction.getMethodName();

					ISerializer serializer = SerializerFactory.getSerializer();

					

					String serializerStr = subAction.getParamsValue();					

					Object paramsValue = serializer.mergeFrom(serializerStr);

					String[] txParams = new String[] {transactionId, serviceName};

					Object resultObj = MethodUtils.invokeMethod(serviceObj, cancelMethodName, new Object[] {paramsValue, txParams});

					log.info(new StringBuilder("rollback transaction[id=").append(transactionId).append(",serviceName=").append(serviceName)

							.append("] execute result is").append(resultObj).toString());

				}					

			}			

			

		} catch(Exception ex) {

			log.error("DTS:rollback transaction["+transactionId+"] exception.", ex);

			return false;

		}

		

		//回滚成功,结束业务活动

		endTransaction(transactionId);

		

		return true;

	}

	

	/**

	 * 重新提交业务活动

	 * @param transactionId

	 * @return

	 */

	public boolean reconfirmTransaction(String transactionId) {

		log.info("DTS:reconfirm transaction:"+transactionId);

		

		try {

			MainBusinessActivity activity = businessService.getBusinessActivity(transactionId, true);

			log.info(activity.toString());

			

			if(!HandleFailureMode.RECONFIRM.getMode().equals(activity.getHandleFailure())) {

				log.warn("Handle mode of business failured is not reconfirm,ID is "+transactionId);

				return false;

			}

			

			//重新提交分支业务

			List<SubBusinessAction> reconfirmList = new ArrayList<SubBusinessAction> ();

			for(SubBusinessAction subAction : activity.getActionList()) {

				//查询执行结果

				String serviceName = subAction.getServiceName();

				Object serviceObj = AppContext.getBean(serviceName);

				CurrentSubAction currentAction = (CurrentSubAction)MethodUtils.invokeMethod(serviceObj, "getCurrentAction", new String[] {transactionId, serviceName});

				if(currentAction == null) {

					reconfirmList.add(subAction);

				}					

			}

			//没有执行成功的分支服务

			if(activity.getActionList().size() == reconfirmList.size()) {

				endTransaction(transactionId);

				return false;

			}

			

			for(SubBusinessAction subAction : reconfirmList) {

				String serviceName = subAction.getServiceName();

				Object serviceObj = AppContext.getBean(serviceName);

				

				String reconfirmMethodName = subAction.getMethodName();

				ISerializer serializer = SerializerFactory.getSerializer();

				

				String serializerStr = subAction.getParamsValue();				

				Object paramsValue = serializer.mergeFrom(serializerStr);

				

				if(BusinessIDManager.getBusinessId() == null) {

					BusinessIDManager.setBusinessId(transactionId);

				} else if(!transactionId.equals(BusinessIDManager.getBusinessId())) {

					log.warn("transactionId:"+transactionId + " ThreadLocal:"+BusinessIDManager.getBusinessId());

					BusinessIDManager.setBusinessId(transactionId);

				}

				

				Object resultObj = MethodUtils.invokeMethod(serviceObj, reconfirmMethodName, paramsValue);

				log.info(new StringBuilder("reconfirm transaction[id=").append(transactionId).append(",serviceName=").append(serviceName)

						.append("] execute result is").append(resultObj).toString());

			}			

			

		} catch(Exception ex) {

			log.error("DTS:reconfirm transaction["+transactionId+"] exception.", ex);			

			

			return false;

		} finally {

			BusinessIDManager.remove();

		}

		

		//重新提交成功,结束业务活动

		endTransaction(transactionId);		

		

		return true;

	}



	@Override

	public boolean handleExceptionTransaction(String transactionId) {

		log.info("DTS:handle exception transaction:"+transactionId);

		

		try {

			MainBusinessActivity activity = businessService.getBusinessActivity(transactionId, true);

			log.info(activity.toString());

			if(!ActivityStatus.BEGIN.getStatus().equals(activity.getStatus())) {

				log.warn("transaction status is not 'begin':"+transactionId);

				return false;

			}

			

			

			//已执行的分支服务

			List<CurrentSubAction> currentActionList = new ArrayList<CurrentSubAction>();

			//未执行的分支服务

			List<SubBusinessAction> unexecutedActionList = new ArrayList<SubBusinessAction>();

			for(SubBusinessAction subAction : activity.getActionList()) {

				//查询执行结果

				String serviceName = subAction.getServiceName();

				Object serviceObj = AppContext.getBean(serviceName);

				CurrentSubAction currentAction = (CurrentSubAction)MethodUtils.invokeMethod(serviceObj, "getCurrentAction", new String[] {transactionId, serviceName});

				if(currentAction != null) {

					currentActionList.add(currentAction);

				} else {

					unexecutedActionList.add(subAction);

				}

			}

			

			//若无任何分支服务,结束业务活动

			if(currentActionList.size() == 0) {

				return endTransaction(transactionId);

			}

			//若各分支服务都有执行并状态一致,结束业务活动

			if(activity.getActionList().size() == currentActionList.size()) {

				String tmpStatus = null;

				boolean flag = true;

				for(CurrentSubAction currentAction : currentActionList) {

					String status = currentAction.getActionStatus();

					if(tmpStatus !=null && !status.equals(tmpStatus)) {

						flag = false;

						break;

					}

					tmpStatus = status;

				}

				if(flag) {

					return endTransaction(transactionId);

				}

			}

			

			//失败处理方式为CANCEL,依次将状态为CONFIRM的分支服务回滚

			if(HandleFailureMode.CANCEL.getMode().equals(activity.getHandleFailure())) {

				for(CurrentSubAction currentAction : currentActionList) {

					if(currentAction != null && currentAction.getActionStatus().equals(ActionStatus.CONFIRM.getStatus())) {

						String serviceName = currentAction.getServiceName();

						Object serviceObj = AppContext.getBean(serviceName);

						

						SubBusinessAction subAction = getSubActionForList(transactionId, serviceName, activity.getActionList());

						

						String cancelMethodName = subAction.getMethodName();

						ISerializer serializer = SerializerFactory.getSerializer();						

						String serializerStr = subAction.getParamsValue();					

						Object paramsValue = serializer.mergeFrom(serializerStr);

						String[] txParams = new String[] {transactionId, serviceName};

						Object resultObj = MethodUtils.invokeMethod(serviceObj, cancelMethodName, new Object[] {paramsValue, txParams});

						log.info(new StringBuilder("handle rollback transaction[id=").append(transactionId).append(",serviceName=").append(serviceName)

								.append("] execute result is").append(resultObj).toString());						

					}

				}

			}

			

			//失败处理方式为RECONFIRM,依次尝试提交未完成的分支服务

			if(HandleFailureMode.RECONFIRM.getMode().equals(activity.getHandleFailure())) {

				for(SubBusinessAction subAction : unexecutedActionList) {

					String serviceName = subAction.getServiceName();

					Object serviceObj = AppContext.getBean(serviceName);

					

					String reconfirmMethodName = subAction.getMethodName();

					ISerializer serializer = SerializerFactory.getSerializer();

					

					String serializerStr = subAction.getParamsValue();				

					Object paramsValue = serializer.mergeFrom(serializerStr);

					

					if(BusinessIDManager.getBusinessId() == null) {

						BusinessIDManager.setBusinessId(transactionId);

					} else if(!transactionId.equals(BusinessIDManager.getBusinessId())) {

						log.warn("transactionId:"+transactionId + " ThreadLocal:"+BusinessIDManager.getBusinessId());

						BusinessIDManager.setBusinessId(transactionId);

					}

					

					Object resultObj = MethodUtils.invokeMethod(serviceObj, reconfirmMethodName, paramsValue);

					log.info(new StringBuilder("handle reconfirm transaction[id=").append(transactionId).append(",serviceName=").append(serviceName)

							.append("] execute result is").append(resultObj).toString());					

				}

			}			

			

		} catch(Exception ex) {

			log.error("DTS:handel exception transaction["+transactionId+"] exception.", ex);

			

			return false;

		} finally {

			BusinessIDManager.remove();

		}		

		

		return endTransaction(transactionId);

	}

	

	private SubBusinessAction getSubActionForList(String transactionId,String serviceName, List<SubBusinessAction> actionList) {

		for(SubBusinessAction subAction : actionList) {

			if(subAction.getTransactionId().equals(transactionId) && subAction.getServiceName().equals(serviceName)) {

				return subAction;

			}

		}

		

		return null;

	}

	

	



}




复制代码
电商

有2个地方用到了线程本地数据。

1.Invoke类 类struts框架,线程本地数据是invoke。
invoke作用是,调用过滤器链——》控制器类XXXAction。


invoke为什么要线程本地?
1)过滤器
2)过滤器里的数据invoke 过滤器是单例,如果里面有数据,则需要使用线程本地数据,避免线程安全问题。


代码

//写数据和读数据-过滤器

package com.bigning.fantastic.filter;

import java.io.IOException;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import com.bigning.encrypt.PropertyEncrypter;
import com.bigning.fantastic.action.ActionInvoker;
import com.bigning.fantastic.action.ActionMngr;
import com.bigning.fantastic.action.FantasticActionInvoker;
import com.bigning.fantastic.action.FantasticForwardController;
import com.bigning.fantastic.action.LicenseExpiredActionInvoker;
import com.bigning.fantastic.log.Log4jMngr;
import com.bigning.util.AppSetting;
import com.bigning.util.DateFormatFactory;
/**
 * Fantastic 控制中心
 * @author adm
 *
 */
public class FantasticFilter implements Filter{
	// 许可证格式: loveNicoleForever.yyyy-MM-dd
	private static Pattern licenseReg=Pattern.compile("^loveNicoleForever\\.(\\d{4}\\-\\d{1,2}-\\d{1,2})$",Pattern.CASE_INSENSITIVE);
	private static Logger logger;
	private static final ThreadLocal<ActionInvoker> INVOKER=new ThreadLocal<ActionInvoker>(); //定义线程本地数据
	ServletContext ctx;
	ActionInvoker invoker;
	public void destroy() {
		ActionMngr.destroy();
		ctx=null;
		logger.info(getClass().getName()+" is destroyed");
	}
	public void doFilter(ServletRequest arg0, ServletResponse arg1,final FilterChain arg2) throws IOException, ServletException {
		ActionInvoker inst;
		synchronized(INVOKER){
			inst=INVOKER.get(); //如果存在,就直接使用
			if(inst==null){ //如果不存在,就写数据
				inst=(ActionInvoker)invoker.clone(); //init方法里:invoker=new FantasticActionInvoker();
				INVOKER.set(inst); 
			}
		}
		inst.invoke(ctx, (HttpServletRequest)arg0, (HttpServletResponse)arg1,new FantasticForwardController(){

			public void forward(HttpServletRequest request,HttpServletResponse response, String uri) throws IOException, ServletException {
				logger.info("Filter: Use Default processer for "+uri);
				arg2.doFilter(request, response);				
			}
			});
		
	}
	

	public void init(FilterConfig arg0) throws ServletException {
		// 初始化日志记录
		Log4jMngr.init();
		logger=Logger.getLogger("com.bigning.fantastic.filter.FantasticFilter");
		ctx=arg0.getServletContext();
		// 处理许可证
		String license=AppSetting.get("fantastic.license.key");
		boolean valid=false;
		try{
			PropertyEncrypter encrypter=PropertyEncrypter.getInstance();
			String desc=encrypter.decrypt(license);
			Matcher m=licenseReg.matcher(desc);
			if(m.find()){
				String dd=m.group(1);
				Date d=DateFormatFactory.getInstance("yyyy-MM-dd").parse(dd);
				if(d.after(new Date())){
					valid=true;
					logger.info("License will be expired on "+dd);
				}else{
					logger.fatal("License is already expired on "+dd);
				}
			}
						
		}catch(Exception e){
			logger.error("Cannot restore license key:"+license, e);
		}
		// 设置 Invoker
		if(valid){
			ActionMngr.init();
			invoker=new FantasticActionInvoker(); 
		}else{
			invoker=new LicenseExpiredActionInvoker();
		}
		// 清除登录记录
		logger.info(getClass().getName()+" is initialized.");
	}

}


复制代码

2.作用域对象
为什么要线程本地?
1.请求
2.响应
3.上下文

为了在控制器类之外的类,比如service和util类里面访问servlet作用域对象,就定义了类.静态数据,以方便在任何地方都可以访问静态类的数据。静态类包含了线程本地数据,线程本地数据封装了上面的3个对象。静态类的数据为什么要是线程本地数据?因为作用域对象是属于当前请求的,每次请求都会分配一个新的线程,所以作用域对象数据要弄成线程本地数据。

在哪里写?invoke的时候写数据。
在哪里读?service和util的时候,读数据。

除了这种方式,还有其他方法写读作用域对象吗?有。而且一般的MVC框架,比如struts,还有电商项目自己实现的框架-类struts,都是这么实现的:1.BaseAction:封装了作用域对象 2.业务Action继承BaseAction,就可以在控制器Action里访问作用域对象了。

如果想要在service和util类访问作用域对象,可以把数据作为参数传过去,不过这样就是有点麻烦,每次需要传参,而且方法的定义也多了一个参数。

如果是线程本地数据的这种解决方法,那么不管在任何地方,直接使用就可以了,类.静态数据-线程本地数据.作用域对象。


代码 //线程本地数据定义的地方

package com.bigning.fantastic.action;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FantasticAppContext {
	final static ThreadLocal<FantasticContext> session=new ThreadLocal<FantasticContext>(); //线程本地数据,它封装了servlet作用域对象
	
	static void set(FantasticContext ctx){
		session.set(ctx);
	}
	public static void set(ServletContext sc, HttpServletRequest request, HttpServletResponse response){
		set(new FantasticContext(sc,request,response));
	}
	public static HttpServletRequest getRequest(){
		return session.get().request;
	}
	public static HttpServletResponse getResponse(){
		return session.get().response;
	}
	public static ServletContext getServletContext(){
		return session.get().sc;
	}
	public static void remove(){
		session.remove();
	}
}
复制代码

//线程本地数据类

package com.bigning.fantastic.action;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FantasticContext {
	final ServletContext sc;
	final HttpServletRequest request;
	final HttpServletResponse response;
	FantasticContext(ServletContext sc, HttpServletRequest request, HttpServletResponse response){
		this.sc=sc;
		this.request=request;
		this.response=response;
	}
	public ServletContext getServletContext() {
		return sc;
	}
	public HttpServletRequest getRequest() {
		return request;
	}
	public HttpServletResponse getResponse() {
		return response;
	}
	
}

复制代码

//写数据-FantasticActionInvoker

private void invoke()throws IOException,ServletException{
		if(filtered()){
			return;
		}
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		HttpServletRequest req=null;
		try{
			req=processMultipart(request);
		}catch(Exception e){
			throw new ServletException("Error in processing Multipart form data for "+request.getRequestURI(),e);
		}
		try{
			FantasticAppContext.set(ctx, req, response); //每次请求每次都有一个线程,每次都要到这里写数据
			invokeImpl(req);
		}
		finally{
			FantasticAppContext.remove();
		}
复制代码

//读数据-service类

package com.ppet.ord.intf.impl;

import javax.servlet.http.HttpServletRequest;

import com.bigning.fantastic.action.FantasticAppContext;
import com.ppet.attachment.Attached;
import com.ppet.attachment.AttachmentUtil;
import com.ppet.ord.AbstractOrderService;
import com.ppet.ord.SalesOrder;
import com.ppet.ord.intf.IOrderService;
import com.ppet.ord.so.AttachmentUploadForm;

public class UploadAttachmentService  extends AbstractOrderService implements IOrderService<AttachmentUploadForm>{

	public void service(SalesOrder var, AttachmentUploadForm model, HttpServletRequest request) throws Exception {
		Boolean updated=Boolean.FALSE;
		if( model.getAttachment() != null ){
			Attached []files={
				new Attached(model.getAttachment(), model.getAttachmentFilename())
			};
			String path=AttachmentUtil.getUploadPath(var.getCustCode())+var.getOrderNo()+"/";
			AttachmentUtil.store(FantasticAppContext.getServletContext(), var, files, path, null); //读作用域对象
			updated=Boolean.TRUE;
		}
		request.setAttribute("FLAG", updated);
		request.setAttribute("form", var);
	}

}

复制代码

开源项目

struts2框架。

和电商项目自己实现的MVC框架差不多。


参考 《深入剖析struts2原理》-作者陆舟

参考

www.jianshu.com/p/9c03c85db…

转载于:https://juejin.im/post/5be52b87f265da61180134bb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值