spring aop + xmemcached 配置service层缓存策略
Memcached 作用与使用 基本介绍
1,对于缓存的存取方式,简言之,就是以键值对的形式将数据保存在内存中。在日常业务中涉及的操作无非就是增删改查。加入缓存机制后,查询的时候,对数据进行缓存,增删改的时候,清除缓存即可。这其中对于缓存的闭合就非常重要,如果缓存没有及时得到更新,那用户就会获取到过期数据,就会产生问题。
2,对于单一业务的缓存管理(数据库中只操作单表),只需生成一个key,查询时,使用key,置入缓存;增删改时,使用key,清除缓存。将key与表绑定,操作相对简单。
3,但是在现实业务中,更多的是对关联表的增删改查(数据库多表操作),业务之间互相关联,数据库中的某张表不止一个业务再进行操作,将缓存拦截在service层,对业务进行缓存,对多表进行缓存。
4,业务层缓存实现策略:
4.1,在缓存中建立一个key为"union_query",value为“hashmap<prefix,uqversion>(‘简称uqmap’)“的缓存,prefix保存的是当前业务操作涉及到的数据库表名的组合(数据库表名的唯一性),使用‘|’分隔(例 prefix=“A|B”,此次业务将操作A表与B表),uqversion是业务版本号,从0开始递增。
4.2,调用一个查询业务时,对数据进行缓存,设置operation为1,告诉cache对象,这是一个缓存操作,例如调用 queryAB(args[])方法时,cache对象切入,将prefix(即”A|B“)与uqversion(初始化为0),存入uqmap中进行缓存。
4.3,将prefix,uqversion,方法明+参数,进行拼接,使用md5进行加密后作为一个key,将方法的结果集作为value,进行缓存。至此缓存成功。
4.4,当第二个请求来调用queryAB时,cache对象切入,首先,查询uqmap对象,使用prefix找到对应的uqversion,然后,通过拼接加密获取key,最后取得结果集进行返回。
4.5,当有一个updateA方法被调用时,设置operation为4,告诉cache对象,这是一个删除缓存的操作,此时prefix的值为“A”,cache对象切入,获取全局的uqmap,遍历其中的prefix,是否包含了表A的名称:如果包含,则更新此prefix的uqversion进行自增,uqversion一旦发生变化,4.3中组合的key将不复存在,业务缓存也就消失了。(对于复杂的updateAB方法,遍历prefix要复杂一点,可以实现)
4.6,当第三个请求来调用queryAB时,可以获取到uqversion,组合成key后,但是没有对应的value。此时确定缓存不存在时,继续正常执行方法,获取结果集,返回给客户的同时,将结果集进行缓存。
5,对于缓存的操作,网上有三种api可以选择(memcached client forjava、spymemcached、xmemcached),具体的好坏,本人在这就不做分析。本人使用的是XMemcached api。
具体实现细节:
1,新建 @interface Annotation{ } 定义一个注解 @Annotation,一个注解是一个类。定义缓存策略。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 用于查找的时候,放置缓存信息
* @author shufeng
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface XmemCache{ /**
* 值为当前操作的表名,表名唯一
* 涉及到多表操作,使用|分隔
*/
String prefix() default ""; /*
* 缓存有效期 设置,单位为秒
* 指定间隔时间,默认值为3600秒(1小时)
* */
int interval() default 3600; /**
* 1 从cache里取值,如果未置入cache,则置入
* 2 replace cache value 未扩展
* 3 replace cache value,并返回旧值 未扩展
* 4 remove cache key 从cache里删除对应的缓存
* 5 remove cache key 从cache里删除对应的缓存,并返回未删除之前的值 未扩展
**/
int operation() default 1;
}
2,memcache基础操作类,对一些常用方法进行封装,对memcachedclient进行配置
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeoutException; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean; import com.node.hlhw.rbac.api.constant.Constant; import net.rubyeye.xmemcached.GetsResponse;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.exception.MemcachedException;
import net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator;
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
import net.rubyeye.xmemcached.utils.AddrUtil; /**
* @author Melody shufeng
* 对memcachedclient进行封装,添加一下常用方法
*/
public class MemcachedOperate implements DisposableBean { /*
* timeout - Operation timeout,if the method is not returned in this
* time,throw TimeoutException timeout - operation timeout,in milliseconds
* exp - An expiration time, in seconds. Can be up to 30 days. After 30
* days, is treated as a unix timestamp of an exact date. value - stored
* data
*/
private static final Logger logger = LoggerFactory.getLogger(MemcachedOperate.class); private static Properties PROPERTIES = new Properties(); private static String MEMCACHED_SETTING = "memcached.properties"; private static MemcachedClient memcachedClient; public static MemcachedClient getClient(){
return memcachedClient;
} /**
* 静态代码块,类加载时,初始化缓存客户端
* 确保只创建一个client实例
* author shufeng
*/
static {
InputStream in = Object.class.getResourceAsStream("/" + MEMCACHED_SETTING);
try {
PROPERTIES.load(in);
} catch (IOException e) {
e.printStackTrace();
}
String servers = PROPERTIES.getProperty("memcached.servers", "");
if (null != servers && !"".equals(servers)) {
try {
logger.debug("启动memcached连接");
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(servers));
builder.setConnectionPoolSize(100);
builder.setFailureMode(true);
builder.setCommandFactory(new BinaryCommandFactory());
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
builder.setTranscoder(new SerializingTranscoder());
memcachedClient = builder.build();
memcachedClient.setEnableHeartBeat(false); // 关闭心跳
memcachedClient.flushAll(); // 清空缓存
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (MemcachedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* @param key
* @return 获取value
*/
public static Object get(String key) {
Object object = null;
try {
object = memcachedClient.get(key);
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (MemcachedException e) {
e.printStackTrace();
}
return object;
} public static void setWithNoReply(String key, int exp, Object value) {
try {
memcachedClient.setWithNoReply(key, exp, value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (MemcachedException e) {
e.printStackTrace();
}
} /**
* 查询联表的业务版本号 如果为空,则初始化
*
* @param prefix
* @return
*/
@SuppressWarnings("unchecked") public static Long getUnionQueryVersion(String prefix) {
try {
Map<String, Long> uqmap = null;
GetsResponse<Object> getsresponse = memcachedClient.gets(Constant.UNION_QUERY);
if (getsresponse == null) {
uqmap = new HashMap<String, Long>();
Long uqversion = new Long(1); // 初始化版本号
uqmap.put(prefix, uqversion);
if (memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, 0)) { // 检测插入之前是否被修改过
return uqversion; // 插入成功
} else { // 插入失败,说明在代码运行期间,已经有其他线程去修改了unionquery的缓存,重新进行查询
return getUnionQueryVersion(prefix);
}
} else { long cas = getsresponse.getCas();
Object uqobj = getsresponse.getValue();
if (uqobj == null) { // 不存在对象
uqmap = new HashMap<String, Long>();
Long uqversion = new Long(1); // 初始化版本号
uqmap.put(prefix, uqversion);
if (memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, cas)) { // 检测插入之前是否被修改过
return uqversion; // 插入成功
} else { // 插入失败,说明在代码运行期间,已经有其他线程去修改了unionquery的缓存,重新进行查询
return getUnionQueryVersion(prefix);
}
} else {
uqmap = (Map<String, Long>) uqobj;
Long uqversion = uqmap.get(prefix);
if (uqversion == null) { // 不存在此业务版本
uqversion = new Long(1); // 初始化版本号
uqmap.put(prefix, uqversion);
if (memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, cas)) { // 检测插入之前是否被修改过
return uqversion; // 插入成功
} else { // 插入失败,说明在代码运行期间,已经有其他线程去修改了unionquery的缓存,重新进行查询
return getUnionQueryVersion(prefix);
}
} else {
return uqversion;
} }
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("getUnionQueryVersion---Exception");
}
return 1L;
} /**
* 查询单表的业务版本号 如果为空,则初始化
*
* @return
*/
public static Long getVersion(String prefix) {
try {
GetsResponse<Object> getsresponse = memcachedClient.gets(prefix);
if (getsresponse == null) {
Long pfversion = new Long(1);
if (memcachedClient.cas(prefix, 0, pfversion, 0)) {
return pfversion;
} else {
return getVersion(prefix);
}
} else {
Object pfobj = getsresponse.getValue();
long cas = getsresponse.getCas();
if (pfobj == null) {
Long pfversion = new Long(1);
if (memcachedClient.cas(prefix, 0, pfversion, cas)) {
return pfversion;
} else {
return getVersion(prefix);
}
} else {
return (Long) pfobj;
}
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("getVersion---Exception");
} return 1L;
} /**
* shufeng 更新 多表版本号
* 由于存在线程安全问题 ,会覆盖uqmap,更新unionquery业务版本号
* 使用cas方法解决线程安全问题
* 更新unionquery中key包含p1或p2或p3的version
* @param prefix
*/
@SuppressWarnings("unchecked")
public static void updateUnionQueryVersion(String prefix) { try {
Map<String, Long> uqmap = null;
GetsResponse<Object> getsresponse = memcachedClient.gets(Constant.UNION_QUERY);
if (getsresponse == null) {
return;
} else {
Object uqobj = getsresponse.getValue();
long cas = getsresponse.getCas();
if (uqobj == null) {
return;
} else {
uqmap = (HashMap<String, Long>) uqobj;
Set<String> uqset = uqmap.keySet(); // 遍历unionquery中的key
Iterator<String> quit = uqset.iterator();
String uqkey = "";
boolean uqflag = false;
while (quit.hasNext()) {
uqkey = quit.next();
if (("|" + uqkey + "|").contains("|" + prefix + "|")) { // key中包含prefix
uqmap.put(uqkey, uqmap.get(uqkey) + 1); // 更新map
uqflag = true;
}
}
if (uqflag) {
if (!memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, cas)) {
updateUnionQueryVersion(prefix);
}
}
}
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("updateUnionQueryVersion---Exception");
}
} /**
* 更新单表版本号
*
* @param prefix
* @return
*/
public static void updateVersion(String prefix) { try {
GetsResponse<Object> getsresponse;
getsresponse = memcachedClient.gets(prefix); if (getsresponse == null) {
return ;
} else {
Object pfobj = getsresponse.getValue();
long cas = getsresponse.getCas();
if (pfobj == null) {
return ;
} else {
Long pfversion = (Long) pfobj;
pfversion += 1;
if (!memcachedClient.cas(prefix, 0, pfversion, cas)) {
updateVersion(prefix);
}
}
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("updateVersion---Exception");
}
} public void shutdown() {
try {
memcachedClient.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void destroy() throws Exception {
shutdown();
} }
3,结合spring aop 配置缓存,使用spring aop来切入业务层加入缓存,与业务进行解耦。使用注解进行方便配置。
import java.lang.reflect.Method; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON;
import com.node.hlhw.common.cache.XmemCache;
import com.node.hlhw.common.digest.Md5Utils; @Component
@Aspect
public class MemcachedAop { @Pointcut("execution (* com.node.hlhw.*.service.impl.*.*(..))")
public void pointcut() {
} // 方法执行前调用
@Before("pointcut()")
public void before() {
} // 方法执行的前后调用
/**
*
* 改进建议:使用uuid作为版本号,减少版本号的读取,直接生成uuid,进行缓存
* 线程安全问题:存在线程安全问题,但是针对于缓存,问题不大。
* 多线程同一时间重复覆盖一个业务id,还是可以更新缓存
*
* @param call
* @throws Throwable
*/
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint call) throws Throwable {
Object result = null; // 检测是否存在memcached客户端实例
if (MemcachedOperate.getClient() == null) {
System.err.println("memcached client not exist");
result = call.proceed();
return result;
}
Signature signature = call.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod(); if(!method.isAnnotationPresent(XmemCache.class)){
result = call.proceed();
return result;
}
XmemCache xmemcache = method.getAnnotation(XmemCache.class); // 获取操作方法
int operation = xmemcache.operation();
// 获取注解前缀,实际使用是为各个业务包名称,一般为表名
String prefix = xmemcache.prefix();
// 无前缀
if(prefix==null||"".equals(prefix)){
result = call.proceed();
return result;
} // 获取注解配置memcached死亡时间 秒单位
int interval = xmemcache.interval();
switch (operation) {
case 1: // 1 从cache里取值,如果未置入cache,则置入
// 判断prefix是否涉及多表,查看是否包含|
if (prefix.contains("|")) {
Long uqversion = MemcachedOperate.getUnionQueryVersion(prefix);
String combinedkey = generCombinedKey(prefix, uqversion, method, call.getArgs());
Object resultobj = MemcachedOperate.get(combinedkey);
if(resultobj == null){
result = call.proceed();
MemcachedOperate.setWithNoReply(combinedkey, interval, JSON.toJSONString(result));// 缓存数据
}else{
Class<?> returnType = ((MethodSignature) signature).getReturnType();
result = JSON.parseObject(resultobj.toString(), returnType);
} } else { // 单表操作 Long pfversion = MemcachedOperate.getVersion(prefix);
String combinedkey = generCombinedKey(prefix, pfversion, method, call.getArgs());
Object resultobj = MemcachedOperate.get(combinedkey);
if(resultobj == null){
result = call.proceed();
MemcachedOperate.setWithNoReply(combinedkey, interval, JSON.toJSONString(result));// 缓存数据
}else{
Class<?> returnType = ((MethodSignature) signature).getReturnType();
result = JSON.parseObject(resultobj.toString(), returnType);
}
}
break;
case 2: // 2 replace cache value
break;
case 3:
break;
case 4: // 4 remove cache key 从cache里删除对应 业务版本的缓存
/*
* 更新unionquery业务版本号
* 0,切割 prefix为p1、p2、p3
* 1,更新prefix为p1或p2或p3的version
* 2,更新unionquery中key包含p1或p2或p3的version
*/
if (prefix.contains("|")) { // 表示涉及到多表,需要清除 单表的缓存,与联表中 包含 当前部分的 缓存
String[] prefixs = prefix.split("\\|"); // 0.切割 prefix为p1、p2、p3
for(String pf : prefixs){
MemcachedOperate.updateVersion(pf); // 1,更新prefix为p1或p2或p3的version
MemcachedOperate.updateUnionQueryVersion(pf);
}
}else{ // 没有涉及到多表的时候
MemcachedOperate.updateVersion(prefix);
MemcachedOperate.updateUnionQueryVersion(prefix);
}
result = call.proceed();
break;
default:
result = call.proceed();
break;
}
return result;
} /**
* 组装key值
* @param key
* @param version
* @param method
* @param args
* @return
*/
private String generCombinedKey(String key, Long version, Method method, Object[] args) {
StringBuffer sb = new StringBuffer();
// 获取方法名
String methodName = method.getName();
// 获取参数类型
Object[] classTemps = method.getParameterTypes();
// 存入方法名
sb.append(methodName); for (int i = 0; i < args.length; i++) {
sb.append(classTemps[i] + "&");
if (null == args[i]) {
sb.append("null");
} else if ("".equals(args[i])) {
sb.append("*");
} else {
String tt = JSON.toJSONString(args[i]);
sb.append(tt);
}
}
sb.append(key);
sb.append(version.toString());
String temp = Md5Utils.getMD5(sb.toString());
return temp; }
}
4,properties文件中配置memcached服务器地址
#host1:port1,host2:port2
memcached.servers=192.168.1.1:11211,192.168.1.2:11211
5,修改spring配置文件,声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。
<aop:aspectj-autoproxy proxy-target-class="true"/>
6,service层使用注解方式切入缓存
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map; import org.apache.ibatis.session.RowBounds;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.config.annotation.Service;
import com.node.hlhw.common.cache.XmemCache;
import com.node.hlhw.common.digest.ApplicationUtils;
import com.node.hlhw.core.service.BaseService;
import com.node.hlhw.core.store.IBaseStore;
import com.node.hlhw.core.store.PageParam;
import com.node.hlhw.rbac.api.dao.UserRoleDao;
import com.node.hlhw.rbac.api.entity.UserRole;
import com.node.hlhw.rbac.api.service.UserRoleService; /**
* @author Melody
* 处理用户角色
*/
@Service(version = "1.0.0")
public class UserRoleServiceImpl extends BaseService<UserRole> implements
UserRoleService { private static final Logger logger = Logger
.getLogger(UserRoleServiceImpl.class); @Autowired
public UserRoleDao userRoleDao; @Override
protected IBaseStore<UserRole> getBaseDao() {
return userRoleDao;
} /*
* 单表操作,prefix为表名,operation为4,只进行缓存的删除操作
*/
@XmemCache(prefix="userrole",operation=4)
public void insertUserRole(UserRole userRole) throws Exception {
userRoleDao.insertUserRole(userRole);
logger.info("插入用户角色数据");
} /* (non-Javadoc)
* 此方法操作了两个表,role与userole,使用‘|’进行分隔
* operation为1,表示缓存操作,对结果集进行缓存
* interval表示缓存时间默认不填为3600秒,也可指定具体时长
*/
@Override
@XmemCache(prefix="role|userrole",interval=3600 , operation=1)
public List<Map<String, Object>> selectUserRoleList(UserRole userrole, PageParam pageParam) throws Exception {
RowBounds rowBounds = new RowBounds(pageParam.getOffset(),pageParam.getLimit());
List<Map<String, Object>> list = userRoleDao.selectUserRoleList(userrole,rowBounds);
return list ;
} @Override
@XmemCache(prefix="userrole" , operation=4)
public void modifyUserRole(UserRole userrole, String[] roleids)throws Exception { //删除所包含的角色
userRoleDao.deleteByUserRole(userrole);
for(String roleid : roleids){
if(!StringUtils.isEmpty(roleid)){
userrole.setCreatetime(new Date());
userrole.setRoleid(roleid);
userrole.setUuid(ApplicationUtils.getUUID());
userRoleDao.insertUserRole(userrole);
}
} } @Override
@XmemCache(prefix="userrole" , operation=1)
public boolean existsRef(String roleids)throws Exception {
String [] roleid = roleids.split(",");
List<String> roleidlist = Arrays.asList(roleid);
return userRoleDao.existsRef(roleidlist)>0?true:false;
}
}
以上代码就是对业务层缓存的实现,小弟认为上述代码还存在许多问题,可能还隐藏着许多bug,还需要在实践中进行不断改进,还希望各路大神感兴趣的朋友,多多提点
spring aop + xmemcached 配置service层缓存策略的更多相关文章
- 基于注解的Spring AOP的配置和使用
摘要: 基于注解的Spring AOP的配置和使用 AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不 ...
- Spring AOP 不同配置方式产生的冲突问题
Spring AOP的原理是 JDK 动态代理和CGLIB字节码增强技术,前者需要被代理类实现相应接口,也只有接口中的方法可以被JDK动态代理技术所处理:后者实际上是生成一个子类,来覆盖被代理类,那么 ...
- spring AOP为什么配置了没有效果?
spring Aop的配置一定要配置在springmvc配置文件中 springMVC.xml 1 <!-- AOP 注解方式 :定义Aspect --> <!-- ...
- 【原】Spring AOP实现对Redis的缓存同步
前言:刚开始采用spring cache作为缓存数据,到后面发现扩展性不灵活,于是基于sprig cache原理自定义一套规则用于缓存数据. 请求过程: 根据请求参数生成Key,后面我们会对生成Key ...
- Spring AOP 事务配置(实现转账事务)
1. 事务特性 事务特性:ACID 原子性:整体 [原子性是指事务包含的所有操作要么全部成功,要么全部失败] 一致性:数据 [一个事务执行之前和执行之后都必须处于一致性状态] 隔离性:并发 [对于任意 ...
- 基于注解的Spring AOP的配置和使用--转载
AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...
- spring事务到底用于service层还是dao层
Spring事务为业务逻辑进行事务管理,保证业务逻辑上数据的原子性. 事务得根据项目性质来细分:事务可以设置到三个层面(dao层.service层和web层). 第一:web层事务,这一般是针对那些安 ...
- spring aop 加在Controller层造成类初始化两遍
写一个测试项目,在配置动态数据源的时候采用的AOP切面到Controller层中,根据参数判断是否切合数据源,结果发现,每次Controller层的类都会初始化两次! 后来测试发现,把切面放到Serv ...
- spring---aop(5)---Spring AOP的配置的背后的配置
写在前面 Spring AOP中Pointcut,dvice 和 Advisor三个概念 1)切入点 Pointcut 在介绍Pointcut之前,有必要先介绍 Join Point(连接点)概念. ...
随机推荐
- POJ1850-Code 递推数学
题目链接:http://poj.org/problem?id=1850 题目大意: 按照字典序对升序排列组成的字母进行编号,给出一个长度不超过10的串,求出它的编号是多少?如果无法进行编号则输出0. ...
- 用 Entity Framework结合Oracle数据库来开发项目
项目需要,要使用Oracle 11g数据库.作为不想写SQL的程序员,所以...... 原先想当然的是使用EF+MSSQL的方式来进行配置.吃了哑巴亏.然后谷歌出了一篇好文,沿着这篇文章进行了搭建,I ...
- iOS之copy、strong使用,block特性
身边一同事,我印象在过去三个月,有两次因为使用“copy”修饰UIKit控件的属性,导致程序崩溃.他还一本正经的说我以前一直使用copy. 好了,到这里我们就不得不说说什么时候使用copy.我的印象中 ...
- HTML5本地存储之Web Storage应用介绍
Web Storage是HTML5引入的一个非常重要的功能,可以在客户端本地存储数据,类似HTML4的cookie,但可实现功能要比cookie强大的多,cookie大小被限制在4KB,Web Sto ...
- dubbo在企业中用得多吗?
看了阿里的dubbo,据说是一个不错的服务框架, 不过,好像Minglisoft.technology搞研发希望各位可以指点学习 想知道其他的公司用这个框架多吗?遇到的问题能否快速解决呢?抉择中...
- Java线程安全性中的对象发布和逸出
发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系. 什么是发布?简单来说就是提供一个对象的引用给作用域之外 ...
- Java并发编程:Callable、Future和FutureTask的实现
启动线程执行任务,如果需要在任务执行完毕之后得到任务执行结果,可以使用从Java 1.5开始提供的Callable和Future 下面就分析一下Callable.Future以及FutureTask的 ...
- Mac iterm2 linux vim 语言问题
- CoreCLR源码探索(六) NullReferenceException是如何发生的
NullReferenceException可能是.Net程序员遇到最多的例外了, 这个例外发生的如此频繁, 以至于人们付出了巨大的努力来使用各种特性和约束试图防止它发生, 但时至今日它仍然让很多程序 ...
- 教你一步搭建Flume分布式日志系统
在前篇几十条业务线日志系统如何收集处理?中已经介绍了Flume的众多应用场景,那此篇中先介绍如何搭建单机版日志系统. 环境 CentOS7.0 Java1.8 下载 官网下载 http://flume ...