前言
目前系统刚启动,骨架刚刚搭建完成,在项目中,使用了一些切面,做一些业务无关的处理。在本文中,将各个切面例举出来,用以加深自己对切面的理解。记得在初学切面的时候,一般文章介绍切面的时候,主要是日志,消息收集等,其实在真实项目中,远不止于此。在现在的项目里面,分别在controller,rpc调用,分页,dao处理,均使用到了切面。下面逐个进行说明。希望本文的阅读者也不吝将项目中使用的切面分享出来。
Controller层
ControllerInterceprot
用于统计每个后台的controller方法的执行时间,并执行打印,切面中用到了TheadLocal。
public class ControllerInterceprot implements HandlerInterceptor{
private static final Logger log = LoggerFactory.getLogger(ControllerInterceprot.class);
private static final ThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("ThreadLocal StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
startTimeThreadLocal.set(System.currentTimeMillis());
String methodName = request.getMethod();
Long userId = SecurityHelper.getCurrentUserId();
log.info("start: method: " + methodName + "userId: " + userId + ", starttime: " + System.currentTimeMillis());
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long excutionTime = System.currentTimeMillis() - startTimeThreadLocal.get();
String methodName = request.getMethod();
Long userId = SecurityHelper.getCurrentUserId();
if (null == ex){
log.info("finish: method: " + methodName + "userId: " + userId + ", excutetime: " + excutionTime);
}else{
log.info("exception finish: method: " + methodName + "userId: " + userId + ", excutetime: " + excutionTime);
}
startTimeThreadLocal.set(null);
}
}
BizExceptionHandler
用于对系统中抛出的异常进行统一处理,主要是捕获异常,多语处理
@ControllerAdvice
public class BizExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(BizExceptionHandler.class);
@Autowired
private BasDbMessageResource messageSource;
@ExceptionHandler(value = BizException.class)
@ResponseBody
public PackVo execute(BizException e, HttpServletRequest request){
PackVo packVo = new PackVo();
packVo.setSuccess(false);
SessionLocaleResolver sessionLocaleResolver = (SessionLocaleResolver) RequestContextUtils.getLocaleResolver(request);
List<BizClientMessage> messages = e.getClientMessages();
for(BizClientMessage message : messages){
packVo.addMsg(message.getCode(),getLocalMessage(message.getCode(),message.getContent(), sessionLocaleResolver.resolveLocale(request)));
}
log.warn("BizException", e);
return packVo;
}
@ExceptionHandler(value = IllegalStateException.class)
@ResponseBody
public PackVo handleXingException(IllegalStateException e,HttpServletRequest request){
PackVo packVo = new PackVo();
packVo.setSuccess(false);
SessionLocaleResolver sessionLocaleResolver = (SessionLocaleResolver) RequestContextUtils.getLocaleResolver(request);
packVo.addMsg(BizExceptionCode.SYS_ERR_XING_SERVICE_FAIL,getLocalMessage(BizExceptionCode.SYS_ERR_XING_SERVICE_FAIL,new String[]{e.getMessage()}, sessionLocaleResolver.resolveLocale(request)));
return packVo;
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public PackVo handleSysException(Exception e,HttpServletRequest request){
e.printStackTrace();
log.error(e.getMessage());
PackVo packVo = new PackVo();
packVo.setSuccess(false);
SessionLocaleResolver sessionLocaleResolver = (SessionLocaleResolver) RequestContextUtils.getLocaleResolver(request);
packVo.addMsg(BizExceptionCode.SYS_ERR_COMMON,getLocalMessage(BizExceptionCode.SYS_ERR_COMMON,new String[]{e.getMessage()}, sessionLocaleResolver.resolveLocale(request)));
return packVo;
}
public String getLocalMessage(String code,String[] values,Locale locale){
log.info("parameter:code=["+code+"] values = ["+values+"]locale=["+locale+"]");
return messageSource.getMessage(code,values,locale);
}
}
RPC层
MasterXingInterceptor
切在了RPC的接口层,在项目中,所有的RPC方法都规定在第一个参数是ClientInfo对象,里面封装了一些用于更新表中的一些公共的字段,比如操作人,语言偏好等。接口会将ClientInfo放到ThreadLocal中去。
@Component
@Aspect
public class MasterXingInterceptor {
private static final Log logger = LogFactory.getLog(MasterXingInterceptor.class);
@Around("execution(* com.best.gwms.master.xing..*.*(..))")
public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
if (args != null && args.length > 0) {
Object firstArg = args[0];
if (firstArg instanceof ClientInfo) {
ClientInfo clientInfo = (ClientInfo) firstArg;
// 把client方法theadlocal里
MasterStatic.setClientInfoTH(clientInfo);
}
}
try {
Object ret = pjp.proceed();
return ret;
} finally {
// 把线程池清空掉,以免出现线程池缓存导致的坑
ThreadLocalUtil.refreshThreadLocal();
}
}
}
public class ThreadLocalUtil {
public static List<ThreadLocal<?>> TL_LIST = new ArrayList<ThreadLocal<?>>();
public static void refreshThreadLocal() {
for (ThreadLocal<?> tl : TL_LIST) {
tl.set(null);
}
}
public static void addThreadLocal2List(ThreadLocal<?> tl) {
TL_LIST.add(tl);
}
}
public class MasterStatic {
private static ThreadLocal<ClientInfo> clientInfoHL = new ThreadLocal<ClientInfo>();
static {
// 放在一个统一的的方法,便于事务结束后统一销毁(线程池缓存的问题)
ThreadLocalUtil.addThreadLocal2List(clientInfoHL);
}
public static ClientInfo getClientInfoHL() {
return clientInfoHL.get();
}
public static void setClientInfoTH(ClientInfo clientInfo) {
clientInfoHL.set(clientInfo);
}
public static Long getDomainId() {
return getClientInfoHL() != null ? getClientInfoHL().getDomainId() : null;
}
public static Long getUserId() {
return getClientInfoHL() != null ? getClientInfoHL().getUserId() : null;
}
}
DAO层
DaoAspectInterceptor
主要作用有两个:1.获取threadlocal中的clientInfo,切在创建方法,更新方法前,设置乐观锁,创建人,创建时间,更新人,更新时间等这些字段,这也要求PO超类,保存公共字段。 2.缓存处理。在get前,查找缓存,如果不命中,再去获取并更新缓存;在更新后,去更新缓存。
@Aspect
@Component
public class MasterDaoAspectInterceptor {
private static final Logger log = LoggerFactory.getLogger(MasterDaoAspectInterceptor.class);
@Autowired
RedisClient redisClient;
@Before("execution(* com.best.gwms.common.base.BasDao+.createPo(..)) && (args(po))")
public void prePersist(AbstractPo po) {
// po.setCreatorId(0L);
// po.setDomainId(10000L);
po.setCreatorId(MasterStatic.getUserId());
po.setDomainId(MasterStatic.getDomainId());
po.setLockVersion(0L);
po.setCreatedTime(new Date());
po.setUpdatedTime(po.getCreatedTime());
}
@Before("execution(* com.best.gwms.common.base.BasDao+.updatePo(..)) && (args(po))")
// @Before("execution(* com.best.gwms.master.dao..*.updatePo(..)) && (args(po))")
public void preUpdate(AbstractPo po) {
// po.setDomainId(10000L);
// po.setUpdatorId(0L);
po.setUpdatorId(MasterStatic.getUserId());
po.setUpdatedTime(new Date());
}
/**
* getpobyid 先从缓存中获取,若缓存中没有,则从数据库中获取数据并存放到缓存中
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("execution(* com.best.gwms.common.base.BasDao+.getPoById(..)) )")
public Object aroundGetPoById(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
Class c = methodSignature.getReturnType();
String simpleName = c.getSimpleName();
Long id = (Long) pjp.getArgs()[0];
String key = redisClient.buildKey(simpleName, id);
if (method.isAnnotationPresent(RedisCache.class)) {
Object value = redisClient.getObject(key);
if (value != null) {
return value;
}
}
Object object = pjp.proceed();
redisClient.setObject(key, object);
return object;
}
/**
* getpobyid 先从缓存中获取,若缓存中没有,则从数据库中获取数据并存放到缓存中
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("execution(* com.best.gwms.common.base.BasDao+.updatePo(..)) )")
public Object aroundUpdatePo(ProceedingJoinPoint pjp) throws Throwable {
// 先清理缓存
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
AbstractPo po = (AbstractPo) pjp.getArgs()[0];
String simpleName = po.getClass().getSimpleName();
Long id = po.getId();
String key = redisClient.buildKey(simpleName, id);
if (method.isAnnotationPresent(RedisCache.class)) {
redisClient.removeValueByKey(key);
}
int count = (int) pjp.proceed();
if (count <= 0) {
log.warn("OptiMistic Lock Exception:" + simpleName);
throw new OptLockException(BizExceptionCode.OPT_LOCK_EXCEPTION);
}
return count;
}
@Around("execution(* com.best.gwms.common.base.BasDao+.createPo(..)) )")
public Object aroundCreatePo(ProceedingJoinPoint pjp) throws Throwable {
// 插入记录的行数
Long count = (Long) pjp.proceed();
if (count.intValue() <= 0) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
throw new DaoException(BizExceptionCode.DAO_EXCEPTION);
}
return count;
}
}
分页
BasPageInterceptor
结合PageHelper,在切面中处理分页相关的计算。本类中切的面有点不精确,还是要在方法名上团队进行一些约定。
@Component
@Aspect
public class BasPageInterceptor {
@Before("execution(* com.best.gwms.master.dao..*.*(..))")
public void sharePage(JoinPoint jpj){
//如果DAO的方法是以count开头,则直接跳过,不进行分页处理
if(StringUtils.startsWith(jpj.getSignature().getName(),"count")){
return;
}
Object[] args = jpj.getArgs();
Integer pageNum = null;
Integer pageSize= null;
String orderBy=null;
for (Object arg : args) {
if (arg instanceof SearchObject) {
SearchObject so = (SearchObject) arg;
orderBy = so.getOrderBy();
pageNum=so.getPageNo();
pageSize=so.getPageSize();
break;
}
}
if (pageNum != null && pageSize != null) {
if (orderBy == null) {
PageHelper.startPage(pageNum,pageSize);
return;
}
PageHelper.startPage(pageNum,pageSize,orderBy);
}
}
}