AOP是個好東西,你值得理解!
前言:最近幾個月由於找工作一直沒時間寫點啥,直到今天入職差不多一個多月了,熟悉了公司的基本架構,對於架構的理解,深刻的瞭解到Aop的重要性,首先還是要感嘆一下架構師的架構設計能力!小弟要學的地方還有很多(~-~),在我們公司的架構中大量的使用到Aop來解決程式碼的複用以及攔截器(其實個人覺得這兩個基本上是相同的,沒啥太大區別),覺得對於Aop結合開發的實際使用過程還是有必要作個分享以及記錄。
簡介:作為Aop大家應該都很熟悉了,它的核心思想就是在方法級別上對某些方法進行攔截,以達到程式碼複用的效果,AOP底層利用的就是JDK動態代理技術以及CGLIB動態代理技術(想了解的同學可以去看看,這裡不講述這些了),它的重點就是使用要掌握反射,掌握了反射,AOP的實際應用就很簡單了。
廢話不多說了,今天主要記錄AOP結合生產環境以達到驚天地泣鬼神的效果~這裡不講原理,直接上程式碼了。
場景:現在給定這樣一個場景,對所有經過http請求的引數,統一利用aop作引數驗證
一、自定義Annotation與AOP是絕配
aop主要是用來攔截的,攔截最常用的方式就是使用execution表示式,但是在很多情形下它並不能解決我們所遇到的問題,業務上的需要,我們要自定義各種標籤用於適用不同的場景。因此最常用的另一種解決方案就是自定義標籤了,對於annotation我就不做補充了,不瞭解的可以看看這位老哥寫的文章(https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * 功能:校驗欄位 * @author ljj * @date 2018年9月12日 */ @Target({ElementType.FIELD, ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface BeanCheckAnnotation { boolean isCheck() default true; /** * @see CheckBeanType * 業務型別 在切入的方法上使用 */ int type() default -1; /** * @see CheckBeanType * 需要檢查業務型別 在檢查的Field上使用 */ int[] types() default {-1}; /**多選擇校驗不為空校驗*/ int moreSelect() default -1; /**校驗欄位最大長度*/ int maxLength() default -1; /**校驗欄位最小長度*/ int minLength() default -1; /**正則表示式校驗*/ String pattem() default ""; }
二、最強輔助
既然是要做引數校驗,那我們就定義一個用於檢驗引數的工具吧!
import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
/**
*
* 功能:
* @author ljj
* @date 2018年9月11日
*/
public class ValidateBean {
public static boolean validate(int type,Object bean) throws IllegalArgumentException, IllegalAccessException{
Class<?> clazz = bean.getClass();
Class<?> superclass = clazz.getSuperclass();
if(superclass!=Object.class){
return validate(type, superclass,bean);
}
return validate(type, clazz, bean);
}
public static boolean validate(int type,Class<?> clazz,Object bean) throws IllegalArgumentException, IllegalAccessException{
//bean為實體類
//獲取Fields
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//獲取欄位的驗證型別
Class<?> subClazz = field.getType();
//獲取欄位的值
Object value = field.get(bean);
boolean flag = checkValue(type,subClazz,value);
if(!flag){
System.out.println("引數"+field.getName()+"不能為空!");
throw new BusinessException("引數"+field.getName()+"不能為空!");
}
//獲取驗證型別
BeanCheckAnnotation checkAnnotation = field.getAnnotation(BeanCheckAnnotation.class);
if(checkAnnotation!=null){
//長度驗證
int minLength = checkAnnotation.minLength();
int maxLength = checkAnnotation.maxLength();
if(!(minLength==-1&&maxLength==-1)){
if(!checkLength(minLength, maxLength, value)){
throw new BusinessException("引數"+field.getName()+(minLength==-1?"":"最小長度為:"+minLength)+(maxLength==-1?"":"最大長度為:"+maxLength));
}
}
//正則表示式驗證
String pattem = checkAnnotation.pattem();
if(StringUtils.isNotBlank(pattem)){
if(!checkPattern(pattem, value)){
throw new BusinessException("引數"+field.getName()+"驗證"+pattem+"不通過!");
}
}
}
}
//bean為陣列
return true;
}
//非空校驗
public static boolean checkValue(int type,Class<?> subClazz,Object value){
if(value==null){
return false;
}
if(subClazz == String.class){
if(StringUtils.isBlank(value.toString())){
return false;
}
}
return true;
}
//長度校驗
public static boolean checkLength(int minLength,int maxLength,Object value){
String v = value.toString();
if(v.length()<minLength||v.length()>maxLength){
return false;
}
return true;
}
//正則驗證
public static boolean checkPattern(String pattern,Object value){
String v = value.toString();
Pattern p = Pattern.compile(pattern);
Matcher matcher = p.matcher(v);
if(matcher.matches()){
return true;
}
return false;
}
}
三、切面攔截我最強!
對於切面攔截,這直接使用註解的方式,這種方式既簡單又方便,你不用嗎?下面我們就定義用於攔截帶標籤的請求吧!,直接上程式碼了(@[email protected])
import org.aspectj.lang.JoinPoint;
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;
/**
*
* 功能:檢驗bean合法性驗證
* @author ljj
* @date 2018年9月12日
*/
@Component
@Aspect
public class BeanCheckAop {
@Pointcut("@annotation(com.zjcx.app.gateway.common.annotation.BeanCheckAnnotation)&&"
+ "execution(public * com.ljj.service..*.*(..))")
public void check(){
}
@Before("check()")
public void debefore(JoinPoint jp) throws IllegalArgumentException, IllegalAccessException{
MethodSignature signature = (MethodSignature)jp.getSignature();
BeanCheckAnnotation annotation = signature.getMethod().getAnnotation(BeanCheckAnnotation.class);
if(annotation!=null){
//獲取引數
Object[] args = jp.getArgs();
//獲取校驗型別
int type = annotation.type();
check(type,args);
}
}
public void check(int type,Object[] beans) throws IllegalArgumentException, IllegalAccessException{
for (Object object : beans) {
ValidateBean.validate(type, object);
}
}
}
總結:上面這幾處程式碼就是用於攔截請求引數並進行驗證的程式碼了,是不是很方便也很簡單,實際上生產環境中常常會有這種需求,判斷一個人能不能寫出高質量的程式碼的第一要求就是你寫的程式碼複用性一定要好,這樣才有助於你對於架構方面的成長。
篇幅有點短,為了強行湊字數,就再補充個列印日誌的切面邏輯給大家
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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
/**
*
* 功能:用於列印日誌
* @author ljj
* @date 2018年9月12日
*/
@Component
@Aspect
public class LoggerAop {
public static final Logger log= LoggerFactory.getLogger(LoggerAop.class);
@Pointcut("execution(public * com.zjcx.app.gateway.controller..*.*(..)) ")
public void log(){
}
@Around("log()")
public Object printLog(ProceedingJoinPoint jp) throws Throwable{
//獲取結果
Object result = jp.proceed();
Object[] args = jp.getArgs();
MethodSignature signature = (MethodSignature)jp.getSignature();
Method method = signature.getMethod();
String methodName = method.getName();
log.info(methodName+"接收到請求訊息,"+JSONObject.toJSONString(args)+"\n");
log.info("執行"+methodName+"方法,"+"返回結果========>"+JSONObject.toJSONString(result));
return result;
}
}