1、什麼是JAVA註解
從JDK5開始,Java增加對元數據的支持,也就是註解,註解與註釋是有一定區別的,可以把註解理解爲代碼裏的特殊標記,這些標記可以在編譯,類加載,運行時被讀取,並執行相應的處理。通過註解開發人員可以在不改變原有代碼和邏輯的情況下在源代碼中嵌入補充信息。
2、JAVA自定義註解創建所依賴的標籤
@Documented
表示使用該註解的元素應被javadoc或類似工具文檔化,它應用於類型聲明,類型聲明的註解會影響客戶端對註解元素的使用。如果一個類型聲明添加了Documented註解,那麼它的註解會成爲被註解元素的公共API的一部分。
@Retention
表示需要在什麼級別保存該註解信息。可選的RetentionPolicy參數包括:
- SOURCE:註解將被編譯器丟棄
- CLASS:註解在class文件中可用,但會被VM丟棄
- RUNTIME:VM將在運行期間保留註解,因此可以通過反射機制讀取註解的信息
@Target
表示該註解可以用於什麼地方,可能的ElementType參數有:
- CONSTRUCTOR:構造器的聲明
- FIELD:域聲明(包括enum實例)
- LOCAL_VARIABLE:局部變量聲明
- METHOD:方法聲明
- PACKAGE:包聲明
- PARAMETER:參數聲明
- TYPE:類、接口(包括註解類型)或enum聲明
@Inherited
表示允許子類繼承父類中的註解
3、快速實戰入門
(1)、引入切面依賴:
<!-- Aspect -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
(2)、定義一個簡單的 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 掃描annotation包 -->
<context:component-scan base-package="annotation" />
<!-- 暴露動態代理 -->
<aop:aspectj-autoproxy expose-proxy="true"/>
</beans>
(3)、創建一個註解標籤
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Inherited
public @interface LeopardAnno {
String key() default "";
String recodeLog() default "";
}
(4)、建立簡單的實現類,使用自定義的註解(key="#name"引用了SPEL表達式,可自行了解,後面會展示如何使用)
@Service
public class AnnoService {
@LeopardAnno(key="#name")
public String getName(String name) {
System.out.println(name + " 進來了!!");
return name;
}
}
(5)、註解切面業務處理
@Component
@Aspect
public class AnnotationAspect {
//這裏將自定義註解作爲形參,@annotation可直接使用形參引入
@Around("@annotation(leo)")
public Object doAround(ProceedingJoinPoint pjp , LeopardAnno leo) throws Throwable{
System.out.println(leo.key());
System.out.println(leo.recodeLog());
System.out.println("-------開始攔截");
System.out.println("類路徑名:"+pjp.getSourceLocation().getWithinType().getName());
System.out.println("方法名:"+pjp.getSignature().getName());
Object proceed = pjp.proceed();
System.out.println("-------結束攔截");
return proceed;
}
}
(6)、基礎搭建結束。寫個測試類運行:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AnnoTest {
@Autowired
private AnnoService annoService;
@Test
public void testOne(){
String name = annoService.getName("leopard");
System.out.println(name);
}
}
運行結果:
#name
null
-------就是攔截
annotation.AnnoService
getName
leopard 進來了!!
-------結束攔截
leopard
到這裏,基本註解功能實現!可能會發現,註解切面獲取的參數el表達式沒轉化,還是#name。
是因爲我們尚未處理佔位符的原因,下面我們寫一個工具類來處理
(7)、EL表達式參數字符處理
public class SpelParser {
private static ExpressionParser ep = new SpelExpressionParser();
/**
* @param key el表達式字符串,佔位符以#開頭
* @param paramterNames 形參名稱,可以理解爲佔位符名稱
* @param args 多數值,可以理解爲佔位真實值
* @return 返回EL表達式參數替換後的字符串值
*/
public static String getKey(String key, String[] paramterNames , Object[] args){
//1、將Key字符串解析爲el表達式
Expression exp = ep.parseExpression(key);
//2、將形參和形參值以配對的方式配置到賦值上下文中
EvaluationContext context = new StandardEvaluationContext();//初始化賦值上下文
if(args.length <= 0){
return null;
}
for (int i = 0; i < args.length; i++) {
context.setVariable(paramterNames[i], args[i]);
}
//3、根據賦值上下文運算EL表達式
return exp.getValue(context, String.class);
}
}
接着在(5)的基礎上,加入私有處理方法
private String getKey(String key, ProceedingJoinPoint pjp) {
//不符合EL表達式佔位符格式,原值返回
if (!(StringUtils.isNotBlank(key) && '#' == key.toCharArray()[0])) {
return key;
}
// 獲取目標方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
// 根據方法獲取行參列表
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(method);
return SpelParser.getKey(key, parameterNames, pjp.getArgs());
}
修改原代碼獲取參數值的方法
System.out.println(getKey(leo.key(),pjp));
System.out.println(getKey(leo.recodeLog(),pjp));
再次運行測試類,得到以下結果:
leopard
null
-------開始攔截
annotation.AnnoService
getName
leopard 進來了!!
-------結束攔截
leopard
結論:註解的實現看起來實現比較繁瑣,但他可以很好的解決我們的代碼冗餘問題。
技術解耦:如緩存操作,權限管理
業務解耦:如日誌記錄,第三方關聯操作