三種方式實現分散式鎖
方案一:資料庫樂觀鎖
樂觀鎖通常實現基於資料版本(version)的記錄機制實現的,比如有一張紅包表(t_bonus),有一個欄位(left_count)記錄禮物的剩餘個數,使用者每領取一個獎品,對應的left_count減1,在併發的情況下如何要保證left_count不為負數,樂觀鎖的實現方式為在紅包表上新增一個版本號欄位(version),預設為0。
異常實現流程
-- 可能會發生的異常情況
-- 執行緒1查詢,當前left_count為1,則有記錄
select * from t_bonus where id = 10001 and left_count > 0
-- 執行緒2查詢,當前left_count為1,也有記錄
select * from t_bonus where id = 10001 and left_count > 0
-- 執行緒1完成領取記錄,修改left_count為0,
update t_bonus set left_count = left_count - 1 where id = 10001
-- 執行緒2完成領取記錄,修改left_count為-1,產生髒資料
update t_bonus set left_count = left_count - 1 where id = 10001
通過樂觀鎖實現
-- 新增版本號控制欄位
ALTER TABLE table ADD COLUMN version INT DEFAULT '0' NOT NULL AFTER t_bonus;
-- 執行緒1查詢,當前left_count為1,則有記錄,當前版本號為1234
select left_count, version from t_bonus where id = 10001 and left_count > 0
-- 執行緒2查詢,當前left_count為1,有記錄,當前版本號為1234
select left_count, version from t_bonus where id = 10001 and left_count > 0
-- 執行緒1,更新完成後當前的version為1235,update狀態為1,更新成功
update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234
-- 執行緒2,更新由於當前的version為1235,udpate狀態為0,更新失敗,再針對相關業務做異常處理
update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234
方案二:基於Redis的分散式鎖
SETNX命令(SET if Not eXists)
語法:SETNX key value
功能:原子性操作,當且僅當 key 不存在,將 key 的值設為 value ,並返回1;若給定的 key 已經存在,則 SETNX 不做任何動作,並返回0。
Expire命令
語法:expire(key, expireTime)
功能:key設定過期時間
GETSET命令
語法:GETSET key value
功能:將給定 key 的值設為 value ,並返回 key 的舊值 (old value),當 key 存在但不是字串型別時,返回一個錯誤,當key不存在時,返回nil。
GET命令
語法:GET key
功能:返回 key 所關聯的字串值,如果 key 不存在那麼返回特殊值 nil 。
DEL命令
語法:DEL key [KEY …]
功能:刪除給定的一個或多個 key ,不存在的 key 會被忽略。
第一種:使用redis的setnx()、expire()方法,用於分散式鎖
- setnx(lockkey, 1) 如果返回0,則說明佔位失敗;如果返回1,則說明佔位成功
- expire()命令對lockkey設定超時時間,為的是避免死鎖問題。
- 執行完業務程式碼後,可以通過delete命令刪除key。
這個方案其實是可以解決日常工作中的需求的,但從技術方案的探討上來說,可能還有一些可以完善的地方。比如,如果在第一步setnx執行成功後,在expire()命令執行成功前,發生了宕機的現象,那麼就依然會出現死鎖的問題
第二種:使用redis的setnx()、get()、getset()方法,用於分散式鎖,解決死鎖問題
- setnx(lockkey, 當前時間+過期超時時間) ,如果返回1,則獲取鎖成功;如果返回0則沒有獲取到鎖,轉向2。
- get(lockkey)獲取值oldExpireTime ,並將這個value值與當前的系統時間進行比較,如果小於當前系統時間,則認為這個鎖已經超時,可以允許別的請求重新獲取,轉向3。
- 計算newExpireTime=當前時間+過期超時時間,然後getset(lockkey, newExpireTime) 會返回當前lockkey的值currentExpireTime。
- 判斷currentExpireTime與oldExpireTime 是否相等,如果相等,說明當前getset設定成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那麼當前請求可以直接返回失敗,或者繼續重試。
- 在獲取到鎖之後,當前執行緒可以開始自己的業務處理,當處理完畢後,比較自己的處理時間和對於鎖設定的超時時間,如果小於鎖設定的超時時間,則直接執行delete釋放鎖;如果大於鎖設定的超時時間,則不需要再鎖進行處理。
import cn.com.tpig.cache.redis.RedisService;
import cn.com.tpig.utils.SpringUtils;
/**
* Created by IDEA
* User: shma1664
* Date: 2016-08-16 14:01
* Desc: redis分散式鎖
*/
public final class RedisLockUtil {
private static final int defaultExpire = 60;
private RedisLockUtil() {
//
}
/**
* 加鎖
* @param key redis key
* @param expire 過期時間,單位秒
* @return true:加鎖成功,false,加鎖失敗
*/
public static boolean lock(String key, int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key, "1");
if(status == 1) {
redisService.expire(key, expire);
return true;
}
return false;
}
public static boolean lock(String key) {
return lock2(key, defaultExpire);
}
/**
* 加鎖
* @param key redis key
* @param expire 過期時間,單位秒
* @return true:加鎖成功,false,加鎖失敗
*/
public static boolean lock2(String key, int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long value = System.currentTimeMillis() + expire;
long status = redisService.setnx(key, String.valueOf(value));
if(status == 1) {
return true;
}
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime < System.currentTimeMillis()) {
//超時
long newExpireTime = System.currentTimeMillis() + expire;
long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
if(currentExpireTime == oldExpireTime) {
return true;
}
}
return false;
}
public static void unLock1(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
redisService.del(key);
}
public static void unLock2(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime > System.currentTimeMillis()) {
redisService.del(key);
}
}
}
public void drawRedPacket(long userId) {
String key = "draw.redpacket.userid:" + userId;
boolean lock = RedisLockUtil.lock2(key, 60);
if(lock) {
try {
//領取操作
} finally {
//釋放鎖
RedisLockUtil.unLock(key);
}
} else {
new RuntimeException("重複領取獎勵");
}
}
Spring AOP基於註解方式和SpEL實現開箱即用的redis分散式鎖策略
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* RUNTIME
* 定義註解
* 編譯器將把註釋記錄在類檔案中,在執行時 VM 將保留註釋,因此可以反射性地讀取。
* @author shma1664
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisLockable {
String[] key() default "";
long expiration() default 60;
}
import javax.annotation.Resource;
import java.lang.reflect.Method;
import com.autohome.api.dealer.util.cache.RedisClient;
import com.google.common.base.Joiner;
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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-28 18:08
* Desc:
*/
@Aspect
@Component
public class RedisLockAop {
@Resource
private RedisClient redisClient;
@Pointcut("execution(* com.autohome.api.dealer.tuan.service.*.*(..))")
public void pointcut(){}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable{
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
String targetName = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
Object[] arguments = point.getArgs();
if (method != null && method.isAnnotationPresent(RedisLockable.class)) {
RedisLockable redisLock = method.getAnnotation(RedisLockable.class);
long expire = redisLock.expiration();
String redisKey = getLockKey(targetName, methodName, redisLock.key(), arguments);
boolean isLock = RedisLockUtil.lock2(redisKey, expire);
if(!isLock) {
try {
return point.proceed();
} finally {
unLock2(redisKey);
}
} else {
throw new RuntimeException("您的操作太頻繁,請稍後再試");
}
}
return point.proceed();
}
private String getLockKey(String targetName, String methodName, String[] keys, Object[] arguments) {
StringBuilder sb = new StringBuilder();
sb.append("lock.").append(targetName).append(".").append(methodName);
if(keys != null) {
String keyStr = Joiner.on(".").skipNulls().join(keys);
String[] parameters = ReflectParamNames.getNames(targetName, methodName);
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keyStr);
EvaluationContext context = new StandardEvaluationContext();
int length = parameters.length;
if (length > 0) {
for (int i = 0; i < length; i++) {
context.setVariable(parameters[i], arguments[i]);
}
}
String keysValue = expression.getValue(context, String.class);
sb.append("#").append(keysValue);
}
return sb.toString();
}
<!-- https://mvnrepository.com/artifact/javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.1-GA</version>
</dependency>
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.log4j.Logger;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-28 18:39
* Desc:
*/
public class ReflectParamNames {
private static Logger log = Logger.getLogger(ReflectParamNames.class);
private static ClassPool pool = ClassPool.getDefault();
static{
ClassClassPath classPath = new ClassClassPath(ReflectParamNames.class);
pool.insertClassPath(classPath);
}
public static String[] getNames(String className,String methodName) {
CtClass cc = null;
try {
cc = pool.get(className);
CtMethod cm = cc.getDeclaredMethod(methodName);
// 使用javaassist的反射方法獲取方法的引數名
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) return new String[0];
int begin = 0;
String[] paramNames = new String[cm.getParameterTypes().length];
int count = 0;
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < attr.tableLength(); i++){
// 為什麼 加這個判斷,發現在windows 跟linux執行時,引數順序不一致,通過觀察,實際的引數是從this後面開始的
if (attr.variableName(i).equals("this")){
begin = i;
break;
}
}
for (int i = begin+1; i <= begin+paramNames.length; i++){
paramNames[count] = attr.variableName(i);
count++;
}
return paramNames;
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(cc != null) cc.detach();
} catch (Exception e2) {
log.error(e2.getMessage());
}
}
return new String[0];
}
}
在需要使用分散式鎖的地方添加註解
/**
* 抽獎介面
* 新增redis分散式鎖保證一個訂單隻有一個請求處理,防止使用者刷禮物,支援SpEL表示式
* redisLockKey:lock.com.autohome.api.dealer.tuan.service.impl.drawBonus#orderId
* @param orderId 訂單id
* @return 抽中的獎品資訊
*/
@RedisLockable(key = {"#orderId"}, expiration = 120)
@Override
public BonusConvertBean drawBonus(Integer orderId) throws BonusException{
// 業務邏輯
}
第三種方案:基於Zookeeper的分散式鎖
利用節點名稱的唯一性來實現獨佔鎖
ZooKeeper機制規定同一個目錄下只能有一個唯一的檔名,zookeeper上的一個znode看作是一把鎖,通過createznode的方式來實現。所有客戶端都去建立/lock/${lock_name}_lock節點,最終成功建立的那個客戶端也即擁有了這把鎖,建立失敗的可以選擇監聽繼續等待,還是放棄丟擲異常實現獨佔鎖。
package com.shma.example.zookeeper.lock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-30 16:09
* Desc:
*/
public class ZookeeperLock implements Lock, Watcher {
private ZooKeeper zk;
private String root = "/locks";//根
private String lockName;//競爭資源的標誌
private String myZnode;//當前鎖
private int sessionTimeout = 30000;
private List<Exception> exception = new ArrayList<Exception>();
/**
* 建立分散式鎖,使用前請確認config配置的zookeeper服務可用
* @param config 127.0.0.1:2181
* @param lockName 競爭資源標誌,lockName中不能包含單詞lock
*/
public ZookeeperLock(String config, String lockName){
this.lockName = lockName;
// 建立一個與伺服器的連線
try {
zk = new ZooKeeper(config, sessionTimeout, this);
Stat stat = zk.exists(root, false);
if(stat == null){
// 建立根節點
zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
exception.add(e);
} catch (KeeperException e) {
exception.add(e);
} catch (InterruptedException e) {
exception.add(e);
}
}
@Override
public void lock() {
if(exception.size() > 0){
throw new LockException(exception.get(0));
}
if(!tryLock()) {
throw new LockException("您的操作太頻繁,請稍後再試");
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
this.lock();
}
@Override
public boolean tryLock() {
try {
myZnode = zk.create(root + "/" + lockName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
return true;
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return tryLock();
}
@Override
public void unlock() {
try {
zk.delete(myZnode, -1);
myZnode = null;
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public Condition newCondition() {
return null;
}
@Override
public void process(WatchedEvent watchedEvent) {
//
}
}
ZookeeperLock lock = null;
try {
lock = new ZookeeperLock("127.0.0.1:2182","test1");
lock.lock();
//業務邏輯處理
} catch (LockException e) {
throw e;
} finally {
if(lock != null)
lock.unlock();
}
利用臨時順序節點控制時序實現
/lock已經預先存在,所有客戶端在它下面建立臨時順序編號目錄節點,和選master一樣,編號最小的獲得鎖,用完刪除,依次方便。
演算法思路:對於加鎖操作,可以讓所有客戶端都去/lock目錄下建立臨時順序節點,如果建立的客戶端發現自身建立節點序列號是/lock/目錄下最小的節點,則獲得鎖。否則,監視比自己建立節點的序列號小的節點(比自己建立的節點小的最大節點),進入等待。
對於解鎖操作,只需要將自身建立的節點刪除即可。
package com.shma.example.zookeeper.lock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
/**
* Created by IDEA
* User: mashaohua
* Date: 2016-09-30 16:09
* Desc:
*/
public class DistributedLock implements Lock, Watcher{
private ZooKeeper zk;
private String root = "/locks";//根
private String lockName;//競爭資源的標誌
private String waitNode;//等待前一個鎖
private String myZnode;//當前鎖
private CountDownLatch latch;//計數器
private int sessionTimeout = 30000;
private List<Exception> exception = new ArrayList<Exception>();
/**
* 建立分散式鎖,使用前請確認config配置的zookeeper服務可用
* @param config 127.0.0.1:2181
* @param lockName 競爭資源標誌,lockName中不能包含單詞lock
*/
public DistributedLock(String config, String lockName){
this.lockName = lockName;
// 建立一個與伺服器的連線
try {
zk = new ZooKeeper(config, sessionTimeout, this);
Stat stat = zk.exists(root, false);
if(stat == null){
// 建立根節點
zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
} catch (IOException e) {
exception.add(e);
} catch (KeeperException e) {
exception.add(e);
} catch (InterruptedException e) {
exception.add(e);
}
}
/**
* zookeeper節點的監視器
*/
public void process(WatchedEvent event) {
if(this.latch != null) {
this.latch.countDown();
}
}
public void lock() {
if(exception.size() > 0){
throw new LockException(exception.get(0));
}
try {
if(this.tryLock()){
System.out.println("Thread " + Thread.currentThread().getId() + " " +myZnode + " get lock true");
return;
}
else{
waitForLock(waitNode, sessionTimeout);//等待鎖
}
} catch (KeeperException e) {
throw new LockException(e);
} catch (InterruptedException e) {
throw new LockException(e);
}
}
public boolean tryLock() {
try {
String splitStr = "_lock_";
if(lockName.contains(splitStr))
throw new LockException("lockName can not contains \\u000B");
//建立臨時子節點
myZnode = zk.create(root + "/" + lockName + splitStr, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(myZnode + " is created ");
//取出所有子節點
List<String> subNodes = zk.getChildren(root, false);
//取出所有lockName的鎖
List<String> lockObjNodes = new ArrayList<String>();
for (String node : subNodes) {
String _node = node.split(splitStr)[0];
if(_node.equals(lockName)){
lockObjNodes.add(node);
}
}
Collections.sort(lockObjNodes);
System.out.println(myZnode + "==" + lockObjNodes.get(0));
if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
//如果是最小的節點,則表示取得鎖
return true;
}
//如果不是最小的節點,找到比自己小1的節點
String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);
} catch (KeeperException e) {
throw new LockException(e);
} catch (InterruptedException e) {
throw new LockException(e);
}
return false;
}
public boolean tryLock(long time, TimeUnit unit) {
try {
if(this.tryLock()){
return true;
}
return waitForLock(waitNode,time);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private boolean waitForLock(String lower, long waitTime) throws InterruptedException, KeeperException {
Stat stat = zk.exists(root + "/" + lower,true);
//判斷比自己小一個數的節點是否存在,如果不存在則無需等待鎖,同時註冊監聽
if(stat != null){
System.out.println("Thread " + Thread.currentThread().getId() + " waiting for " + root + "/" + lower);
this.latch = new CountDownLatch(1);
this.latch.await(waitTime, TimeUnit.MILLISECONDS);
this.latch = null;
}
return true;
}
public void unlock() {
try {
System.out.println("unlock " + myZnode);
zk.delete(myZnode,-1);
myZnode = null;
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
public void lockInterruptibly() throws InterruptedException {
this.lock();
}
public Condition newCondition() {
return null;
}
public class LockException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LockException(String e){
super(e);
}
public LockException(Exception e){
super(e);
}
}
}
相關推薦
三種方式實現分散式鎖
方案一:資料庫樂觀鎖 樂觀鎖通常實現基於資料版本(version)的記錄機制實現的,比如有一張紅包表(t_bonus),有一個欄位(left_count)記錄禮物的剩餘個數,使用者每領取一個獎品,對應的left_count減1,在併發的情況下如何要保證left_count不為負數,樂觀鎖的實現方式為在紅包表
詳解Linux搭建vsftp服務器通過三種方式實現文件傳輸
x86 sys fig passwd 問題: mage vpd cee 啟用 概述 FTP(File Transfer Protocol)中文稱為“文件傳輸協議”。用於Internet上的控制文件的雙向傳輸。 工作原理 一、主動模式: 1、客戶端通過用戶名和密碼登錄服務器
WPFの三種方式實現快捷鍵
原文: WPFの三種方式實現快捷鍵 最近,對wpf新增快捷鍵的方式進行了整理。主要用到的三種方式如下: 一、wpf命令: 資源中新增命令 <Window.Resources> <RoutedUICommand x:Key="ToolCapClick" Text
實現執行緒的第三種方式 實現Callable介面
package com.juc; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /**
三種方式實現java單例
java的單例模式主要分為:餓漢模式和懶漢模式 餓漢模式:餓漢很飢餓,所以直接先new出來,再提供。 餓漢模式程式碼:載入class檔案的時候就例項化。 package com.jkf.redis; public class HungryManSingleto
三種方式實現遠端restful 介面呼叫
1,基本介紹 Restful介面的呼叫,前端一般使用ajax呼叫,後端可以使用的方法比較多, 本次介紹三種: 1.HttpURLConnection實現 2.HttpClient實現 3.Spring的RestTemplate
以下是JAVA中三種方式實現檔案字元統計
以下是JAVA中三種方式實現檔案字元統計 package com.lyc.gui; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.Ha
三種方式實現二維碼(java)
一. 通過使用zxing方式實現: jar準備: https://github.com/zxing/zxing 下載原始碼,將core/src/main/Java/下的所有檔案和javase/src/main/java/下的所有檔案一起打成jar檔案zxing.jar
Android 三種方式實現圓形ImageView
所有方式均繼承了ImageView 圓形圖片實現一:BitmapShader package com.open.widget; import android.content.Context; import android.graphics.Bitmap; impor
vue三種方式實現:全選、反選、全不選
方法一:v-model 與 [{checked:true},…] <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="vi
python使用遞迴、尾遞迴、迴圈三種方式實現斐波那契數列
在最開始的時候所有的斐波那契程式碼都是使用遞迴的方式來寫的,遞迴有很多的缺點,執行效率低下,浪費資源,還有可能會造成棧溢位,而遞迴的程式的優點也是很明顯的,就是結構層次很清晰,易於理解 可以使用迴圈的方式來取代遞迴,當然也可以使用尾遞迴的方式來實現。
【框架】[Spring]AOP攔截-三種方式實現自動代理
這裡的自動代理,我講的是自動代理bean物件,其實就是在xml中讓我們不用配置代理工廠,也就是不用配置class為org.springframework.aop.framework.ProxyFactoryBean的bean。 總結了一下自己目前所學的知識
【Android進度條】三種方式實現自定義圓形進度條ProgressBar
總結了3種方法: 1.多張圖片切換 2.自定義顏色 3.旋轉自定義圖片 其它: Android自定義控制元件NumberCircleProgressBar(圓形進度條)的實現:點選開啟連結 橫線帶數字進度條:點選開啟連結
三種方式實現:三欄佈局(聖盃佈局,雙飛翼佈局前端面試的佈局問題)
1.float+margin效果如下:html程式碼如下: <div class="main">im center</div> <div class="left">
三種方式實現input的keyup延時事件
從事IT兩年了,第一次寫技術文章,之所以一直不敢寫,因為越做這行越感覺自己菜(此處容我做一個悲傷的表情...) 今天做一個keyup的延時事件,對看清楚了,是keyup,不是男性延時! 網上搜了的方法實現有些困難,後來找大神同事幫忙想了幾個辦法出,得以解決。 一、網上找到的
三種方式實現Android頁面底部導航欄
我們在Android手機上使用新浪微博和QQ等一些軟體時,經常會遇到類似下面這種頁面底部導航欄的控制元件,使用這種導航欄可以在手機螢幕的一頁中顯示儘可能多的內容,如下圖所示: 下面我將實現這種導航欄的三種方法總結如下: 一、使用TabHost實現(TabHost在新版的A
android三種方式實現自由移動的view
public class MoveView extends ImageView { private int x, y; private int r, l, t, b; public MoveView(Context context) { this(context, n
Thinking in Java---執行緒通訊+三種方式實現生產者消費者問題
前面講過執行緒之間的同步問題;同步問題主要是為了保證對共享資源的併發訪問不會出錯,主要的思想是一次只讓一個執行緒去訪問共享資源,我們是通過加鎖的方法實現。但是有時候我們還需要安排幾個執行緒的執行次序,而在系統內部執行緒的排程是透明的,沒有辦法準確的控制執行緒的切
三種方式實現限制IP訪問
方式一:Linux防火牆實現 #阻止所有IP訪問 iptables -A INPUT -s 0.0.0.0/0 -p tcp --dport 80 -j DROP #然後再新增白名單 iptables
三種方式實現自定義圓形頁面載入中效果的進度條,包含一個好看的Android UI
效果圖如下:下載地址 樣式一、通過動畫實現定義res/drawable/loading.xml如下: <?xml version="1.0" encoding="UTF-8"?> <animation-list android:oneshot=