基於SpringBoot的後臺管理系統(Enchache配置、全域性異常處理(重點))(四)

Guo_1_9發表於2019-02-28

3、Spring Boot 快取配置、全域性異常處理

說明

如果您有幸能看到,請認閱讀以下內容;

  • 1、本專案臨摹自abel533的Guns,他的專案 fork 自 stylefengGuns!開源的世界真好,可以學到很多知識。

  • 2、版權歸原作者所有,自己只是學習使用。跟著大佬的思路,希望自己也能變成大佬。gogogo》。。

  • 3、目前只是一個後臺模組,希望自己技能增強到一定時,可以把stylefeng 的 [Guns]融合進來。

  • 4、note裡面是自己的學習過程,菜鳥寫的,不是大佬寫的。內容都是大佬的。

  • 5、如有拼寫錯誤,還請見諒。目前的桌子不適合打字,本文只為自己記錄.

問大家一個問題,你們在看本文的時候,覺得哪裡有需要修改的地方?內容和格式方面,歡迎大家提出來。

目錄

  • 1、SpringBoot第一站:分析了啟動類。還有各種自動配置的原始碼點這裡
  • 2、SpringBoot第二站:定義了異常、註解、Node節點、Page點這裡
  • 3、SpringBoot第三站:SpringBoot資料來源配置、Mybatis配置、日誌記錄點這裡
  • 4、SpringBoot第四站:SpringBoot快取配置、全域性異常處理點這裡

昨天看了資料來源、日誌記錄的配置,我們今天再來看看快取配置。

快取配置

該專案全部基於JavaConfig,除enchache.xml,@EnableCaching註解的意思和cache:annotation-diver的工作方式是相同的。它會都會建立一個切面並觸發Spring快取註解的切點。還有一點需要注意的是EhCacheCacheManager管理器。還有SimpleCacheManager,ConcurrentMapCacheManager.Spring Data又提供了RedisCacheManager.具體請看《Spring實戰》,這本書非常不錯,直接再次推薦。

/**
 * 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;
    }
}

複製程式碼

1、利用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,注意,這裡定義成抽象的。因為抽象類天生就是用來被繼承的。

那什麼時候使用抽象類和介面呢:

  • 1、如果你擁有一些方法想讓他們中的一些預設實現,那麼使用抽象類。
  • 2、如果你想實現多重繼承,那麼你必須使用介面。由於java不支多繼承,子類不能夠繼承多個類,但可以實現多個介面
  • 3、如果基本功能在不斷改變,那麼就需要使用抽象類。如果不斷改變基本功能並且使用介面 ,那麼就需要改變所有實現了該介面的類。
/**
 * 快取工廠基類
 */
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()方法,這裡使用了雙重檢查機制,還有延時載入(建立)。有沒有想起單例模式啊,直接貼一段程式碼

關鍵點是使用了volatilesynchronized保證了可見性和同步性。後者可以用在方法上,程式碼塊上,具體內容看這裡吧,不展開了友情提示.

主要作用:延遲初始化降低了初始化類或建立例項的開銷,但也增加了訪問被延遲初始化的欄位的開銷。正常初始化要優於延遲載入,

如果確實要對例項欄位使用多執行緒的安全的延遲初始化,使用基於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狀態先不關注。

基於SpringBoot的後臺管理系統(Enchache配置、全域性異常處理(重點))(四)
基於SpringBoot的後臺管理系統(Enchache配置、全域性異常處理(重點))(四)
/**
 * 全域性的的異常攔截器(攔截所有的控制器)(帶有@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);
    }
}
複製程式碼

異常處理看得也差不多了,接下來看什麼好呢?持續關注,

相關文章