若是您有幸能看到,請認閱讀如下內容;html
一、本項目臨摹自abel533的Guns,他的項目 fork 自 stylefeng 的 Guns!開源的世界真好,能夠學到不少知識。java
二、版權歸原做者全部,本身只是學習使用。跟着大佬的思路,但願本身也能變成大佬。gogogo》。。git
三、目前只是一個後臺模塊,但願本身技能加強到必定時,能夠把stylefeng 的 [Guns]融合進來。github
四、note裏面是本身的學習過程,菜鳥寫的,不是大佬寫的。內容都是大佬的。緩存
五、若有拼寫錯誤,還請見諒。目前的桌子不適合打字,本文只爲本身記錄.安全
問你們一個問題,大家在看本文的時候,以爲哪裏有須要修改的地方?內容和格式方面,歡迎你們提出來。服務器
昨天看了數據源、日誌記錄的配置,咱們今天再來看看緩存配置。多線程
該項目所有基於JavaConfig,除enchache.xml
,@EnableCaching註解的意思和cache:annotation-diver的工做方式是相同的。它會都會建立一個切面並觸發Spring緩存註解的切點。還有一點須要注意的是EhCacheCacheManager
管理器。還有SimpleCacheManager,ConcurrentMapCacheManager.Spring Data又提供了RedisCacheManager.具體請看《Spring實戰》,這本書很是不錯,直接再次推薦。app
/** * ehcache配置 */
@Configuration
@EnableCaching
public class EhCacheConfig {
/** * EhCache的配置 */
@Bean
public EhCacheCacheManager cacheManager(CacheManager cacheManager) {
return new EhCacheCacheManager(cacheManager);
}
/** * EhCache的配置 */
@Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
複製代碼
一、利用Ehcache框架對常常調用的查詢進行緩存,從而提升系統性能。仍是先看接口定義,須要注意的是get()方法使用了泛型.框架
/** * 通用緩存接口 */
public interface ICache {
void put(String cacheName, Object key, Object value);
<T> T get(String cacheName, Object key);
@SuppressWarnings("rawtypes")
List getKeys(String cacheName);
void remove(String cacheName, Object key);
void removeAll(String cacheName);
<T> T get(String cacheName, Object key, ILoader iLoader);
<T> T get(String cacheName, Object key, Class<? extends ILoader> iLoaderClass);
}
--------------------------------------------------------------------------------
/** * 數據重載 */
public interface ILoader {
Object load();
}
複製代碼
接下來看下基礎CacheFactory,注意,這裏定義成抽象的。由於抽象類天生就是用來被繼承的。
那何時使用抽象類和接口呢:
/** * 緩存工廠基類 */
public abstract class BaseCacheFactory implements ICache {
@SuppressWarnings("unchecked")
public <T> T get(String cacheName, Object key, ILoader iLoader) {..略..}
@SuppressWarnings("unchecked")
public <T> T get(String cacheName, Object key, Class<? extends ILoader> iLoaderClass) {
Object data = get(cacheName, key);
if (data == null) {
try {
ILoader dataLoader = iLoaderClass.newInstance();
data = dataLoader.load();
put(cacheName, key, data);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return (T) data;
}
}
複製代碼
接着在看看具體的EnCacheFactory
,這裏你本身也能夠定義其餘緩存工廠,擴展的時候只要繼承BaseCacheFactory
就行。
第一點須要注意的是這裏使用了org.slf4j.LoggerFactory
第二點須要注意的是靜態getCacheManager()
方法,這裏使用了雙重檢查機制,還有延時加載(建立)。有沒有想起單例模式啊,直接貼一段代碼
關鍵點是使用了volatile
和synchronized
保證了可見性和同步性。後者能夠用在方法上,代碼塊上,具體內容看這裏吧,不展開了友情提示.
主要做用:延遲初始化下降了初始化類或建立實例的開銷,但也增長了訪問被延遲初始化的字段的開銷。正常初始化要優於延遲加載,
若是確實要對實例字段使用多線程的安全的延遲初始化,使用基於volatile的初始化,若是須要對靜態字段使用線程安全的初始化,則使用基於類的初始化方案。
/** * Created by guo on 2018/1/29. */
public class SafeDoubleCheckedLocking {
private volatile static Instacen instance;
public static Instacen getInstance() {
if(instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null) {
instance = new Instacen();
}
}
}
return instance;
}
}
class Instacen{
}
--------------------------對比-------------------------------------------------
/** * Created by guo on 2018/1/29. * 基於類的初始化解決方案 */
public class InstanceFactory {
private static class InstanceHolder{
public static Instance instance = new Instance();
}
public static Instacen getInstance() {
return InstanceHolder.instance;
}
}
class Instance extends Instacen {
}
複製代碼
回到咱們Ehcache緩存工廠吧,重點是CacheManager
.Spring框架底層有許多個Manager。如DataSourceTransactionManager
.還有就是建立CacheManager的create()
方法。人家也使用了雙重檢查,延遲加載。看見singleton了麼。private static volatile CacheManager singleton;
public static CacheManager create() throws CacheException {
if(singleton != null) {
LOG.debug("Attempting to create an existing singleton. Existing singleton returned.");
return singleton;
} else {
Class var0 = CacheManager.class;
synchronized(CacheManager.class) {
if(singleton == null) {
singleton = newInstance();
} else {
LOG.debug("Attempting to create an existing singleton. Existing singleton returned.");
}
return singleton;
}
}
}
複製代碼
這裏是調用cacheManager.getCache
來獲取緩存。你們仍是親自看看源碼把,這裏只是本身明白了討論,記錄下。
/** * Ehcache緩存工廠 */
public class EhcacheFactory extends BaseCacheFactory {
private static CacheManager cacheManager;
private static volatile Object locker = new Object();
private static final Logger log = LoggerFactory.getLogger(EhcacheFactory.class);
private static CacheManager getCacheManager() {
if (cacheManager == null) {
synchronized (EhcacheFactory.class) {
if (cacheManager == null) {
cacheManager = CacheManager.create();
}
}
}
return cacheManager;
}
static Cache getOrAddCache(String cacheName) {
CacheManager cacheManager = getCacheManager();
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
synchronized(locker) {
cache = cacheManager.getCache(cacheName);
if (cache == null) {
log.warn("沒法找到緩存 [" + cacheName + "]的配置, 使用默認配置.");
cacheManager.addCacheIfAbsent(cacheName);
cache = cacheManager.getCache(cacheName);
log.debug("緩存 [" + cacheName + "] 啓動.");
}
}
}
return cache;
}
-----------------------省略幾個----------------------------------------------
public void put(String cacheName, Object key, Object value) {
getOrAddCache(cacheName).put(new Element(key, value));
}
public <T> T get(String cacheName, Object key) {
Element element = getOrAddCache(cacheName).get(key);
return element != null ? (T)element.getObjectValue() : null;
}
public void remove(String cacheName, Object key) {
getOrAddCache(cacheName).remove(key);
}
}
複製代碼
接着咱們來看幾個常量的定義及實現
/** * 獲取被緩存的對象(用戶刪除業務) */
String getCacheObject(String para);
-----------------------------------------------------------------------------------
/** * 獲取被緩存的對象(用戶刪除業務) */
@Override
public String getCacheObject(String para) {
return LogObjectHolder.me().get().toString(); //還有一個set()記得嗎?
}
複製代碼
配置完了你總的使用,看代碼。先不關注權限那塊。CacheKit
是一個工具類。
/** * 刪除角色 */
@RequestMapping(value = "/remove")
@BussinessLog(value = "刪除角色", key = "roleId", dict = Dict.DeleteDict)
@Permission(Const.ADMIN_NAME)
@ResponseBody
public Tip remove(@RequestParam Integer roleId) {
if (ToolUtil.isEmpty(roleId)) {
throw new BussinessException(BizExceptionEnum.REQUEST_NULL);
}
//不能刪除超級管理員角色
if(roleId.equals(Const.ADMIN_ROLE_ID)){
throw new BussinessException(BizExceptionEnum.CANT_DELETE_ADMIN);
}
//緩存被刪除的角色名稱
LogObjectHolder.me().set(ConstantFactory.me().getSingleRoleName(roleId));
roleService.delRoleById(roleId);
//刪除緩存
CacheKit.removeAll(Cache.CONSTANT);
return SUCCESS_TIP;
}
---------------------------工具類------------------------------------------------
/** * 緩存工具類 */
public class CacheKit {
private static ICache defaultCacheFactory = new EhcacheFactory(); //這裏建立Encache工廠。
public static void put(String cacheName, Object key, Object value) {
defaultCacheFactory.put(cacheName, key, value);
}
public static void removeAll(String cacheName) {
defaultCacheFactory.removeAll(cacheName);
}
}
複製代碼
到這裏緩存部分算是結束了,再次說明,只是本身記錄過程,要讓我實現,目前不現實,還須要本身請自看看源碼,跑一遍。
接下來,咱們在看看控制統一的異常攔截機制。這裏用到了切面的思想。第一眼看到的是@ControllerAdvice。這是什麼東東,看圖說話。GlobalExceptionHandler
所有代碼點這裏點這裏。ResponseStatus狀態先不關注。
/** * 全局的的異常攔截器(攔截全部的控制器)(帶有@RequestMapping註解的方法上都會攔截) */
@ControllerAdvice
public class GlobalExceptionHandler {
private Logger log = LoggerFactory.getLogger(this.getClass());
/** * 攔截業務異常 * * @author fengshuonan */
@ExceptionHandler(BussinessException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorTip notFount(BussinessException e) {
LogManager.me().executeLog(LogTaskFactory.exceptionLog(ShiroKit.getUser().getId(), e));
getRequest().setAttribute("tip", e.getMessage());
log.error("業務異常:", e);
return new ErrorTip(e.getCode(), e.getMessage());
}
/** * 用戶未登陸 */
@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public String unAuth(AuthenticationException e) {
log.error("用戶未登錄:", e);
return "/login.html";
}
/** * 攔截未知的運行時異常 */
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorTip notFount(RuntimeException e) {
LogManager.me().executeLog(LogTaskFactory.exceptionLog(ShiroKit.getUser().getId(), e));
getRequest().setAttribute("tip", "服務器未知運行時異常");
log.error("運行時異常:", e);
return new ErrorTip(BizExceptionEnum.SERVER_ERROR);
}
}
複製代碼
異常處理看得也差很少了,接下來看什麼好呢?持續關注,