一个request引发的bug
有很多错误由于需要是多线程是才会发生,导致经常在开发时很难发现,
import java.lang.reflect.ParameterizedType; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.support.WebApplicationContextUtils; import com.google.gson.Gson; import com.qdyq.common.criteria.Criteria; import com.qdyq.common.orm.BaseEntity; import com.qdyq.common.orm.BaseMapper; import com.qdyq.common.orm.MySqlSessionFactoryBean; import qdyq.api.service.CommonService; import qdyq.api.service.exception.MapperNameErrorException; @Service("common") @SuppressWarnings({ "rawtypes", "unchecked" }) public class CommonServiceImpl implements CommonService { //private String entityName; private ThreadLocal<String> entityNameSafe = new ThreadLocal<String>(); @Autowired private MySqlSessionFactoryBean sqlSessionFactory; public List list(Criteria criteria) { BaseMapper mapper = getMapper(); System.out.println(mapper.getClass().getName()); return mapper.getBy(criteria); } public BaseEntity getById(Long id) { BaseMapper mapper = getMapper(); return mapper.getById(id); } public int save(String entityJson) { BaseMapper mapper = getMapper(); BaseEntity entity = (BaseEntity) new Gson().fromJson(entityJson, this.getEntityClass()); if(entity==null){ System.err.println(Thread.currentThread().getId() + entityJson + this.entityNameSafe.get()); } if (entity.getId() == null) return mapper.insert(entity); else return mapper.update(entity); } public int save(BaseEntity entity) { this.setEntityName(entity.getClass().getSimpleName()); BaseMapper mapper = getMapper(); System.err.println(mapper.getClass().getName() + Thread.currentThread().getId()); if (entity.getId() == null) return mapper.insert(entity); else return mapper.update(entity); } public int countBy(Criteria criteria) { BaseMapper mapper = getMapper(); return mapper.countBy(criteria); } public void setEntityName(String entityName) { this.entityNameSafe.set(entityName); //this.entityName = entityName; } public Class getEntityClass() { String daoName = this.sqlSessionFactory.getPkgName() + "." + this.entityNameSafe.get() + "Dao"; Class daoInterface; try { daoInterface = Class.forName(daoName); return (Class) ((ParameterizedType) daoInterface.getGenericInterfaces()[0]).getActualTypeArguments()[0]; } catch (ClassNotFoundException e) { throw new MapperNameErrorException("无效的entityName" + daoName, e, daoName); } catch (Exception e) { throw new MapperNameErrorException("未注册的entityName" + daoName, e, daoName); } } public List listAss(Criteria criteria) { BaseMapper mapper = getMapper(); return mapper.getAssBy(criteria); } public List all() { BaseMapper mapper = getMapper(); return mapper.findAll(); } public int delete(Object[] id) { BaseMapper mapper = getMapper(); return mapper.deleteByIds(id); } protected BaseMapper getMapper(){ String daoName = this.sqlSessionFactory.getPkgName() + "." + this.entityNameSafe.get() + "Dao"; Class daoInterface; System.err.println(daoName + Thread.currentThread().getId()); HttpServletRequest request = null; try{ daoInterface = Class.forName(daoName); request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext()); return (BaseMapper) context.getBean(daoInterface); }catch(Exception e){ throw new RuntimeException("无法获取到mapper:" + this.entityNameSafe.get()); } } public int saveAll(List<BaseEntity> list) { BaseMapper mapper = getMapper(); return mapper.insertAll(list); } public int removeBy(Criteria criteria){ BaseMapper mapper = getMapper(); return mapper.removeBy(criteria); } public static void main(String[] args){ //String json = "{id:485,lastTraceDate:'2017-11-09T10:20:37.293Z'}"; } }
这是一个公共的类,能满足一般的增删改查功能,通过参数,entityName来确定具体使用哪个mapper来完成任务,mapper只要有一个公共的基类,就可以实现上述
功能,但是由于网络请求是多线程的,所以及易导致entityName的冲突,因为原先的entityName只是一个本地字符串,而springmvc的对象又是单例的,这就导致错误的发生
ThreadLocal<String>是一种解决办法,第二种办法是把该对象配置成原型模式,也可以避免错误的发生.
既然类成员变量容易导致错误,哪么request会不会导致错误,因为这次错误引起了思考果断做了测试,
@ResponseBody @RequestMapping(value = "/save/{entityName}", method = RequestMethod.POST) public Object save(@PathVariable String entityName, String entityJson,HttpServletRequest request) { this.getService().setEntityName(entityName); System.err.println("============================"); System.err.println(request.hashCode()); System.err.println(this.request.hashCode()); System.err.println(this.request.getParameter("entityJson")); System.err.println(this.request.getClass().getName()); System.err.println(request.getClass().getName()); System.err.println("============================"); return getService().save(entityJson); }
this.request是通过@Autowired注入的,打印结果到显示this.request始终是一个对象,但是它的获取结果却始终都是正确的,因为它并不是一个对象而是一个代理,也就是说它是线程安全的.但是作为参数传进来的request就不是线程安全的,如果你把它保存在一个成员变量里,后果是很严重的,因为使用过期的request会导致现有request参数的丢失,具体原因还不太清楚,感觉应该是request这个对象里使用缓存之类的东西造成的.