AOP面向切面編程是縱向編程,在spring框架中很多注解都是基于aop做的功能增強(qiáng)奔害,原理是java的動(dòng)態(tài)代理模式尔邓。
先理解一下基本概念
切入點(diǎn)(PointCut)
在需要做增強(qiáng)功能的方法上添加自定義注解實(shí)現(xiàn)功能增強(qiáng)掂名,這個(gè)方法就是切入點(diǎn),@Pointcut对湃。
切面(Aspect)
有了切入點(diǎn),把需要增強(qiáng)的功能寫入到某個(gè)實(shí)現(xiàn)類里遗淳,這個(gè)類就叫做切面拍柒,里面可以聲明前置增強(qiáng)、后置增強(qiáng)屈暗、環(huán)繞增強(qiáng)拆讯、發(fā)生異常處理等操作
連接點(diǎn)(JoinPoint)
在切點(diǎn)(即指需要增強(qiáng)的方法)上的哪一個(gè)執(zhí)行階段加入增加代碼,這個(gè)執(zhí)行階段就叫連接點(diǎn)恐锦,比如:方法調(diào)用前@Before往果,方法調(diào)用后@After,發(fā)生異常時(shí)@AfterThrowing等等一铅。
通知(Advice)
連接點(diǎn)里面聲明的代碼處理就是通知陕贮,如@Before注解方法里面的具體代碼操作。
目標(biāo)對(duì)象(Target Object)
被一個(gè)或多個(gè)切面所通知的對(duì)象潘飘,即為目標(biāo)對(duì)象肮之。
AOP代理對(duì)象(AOP Proxy Object)
AOP代理是AOP框架所生成的對(duì)象,該對(duì)象是目標(biāo)對(duì)象的代理對(duì)象卜录。代理對(duì)象能夠在目標(biāo)對(duì)象的基礎(chǔ)上戈擒,在相應(yīng)的連接點(diǎn)上調(diào)用通知。
織入(Weaving)
將切面切入到目標(biāo)方法之中艰毒,使目標(biāo)方法得到增強(qiáng)的過(guò)程被稱之為織入筐高。
示例:
maven在普通springboot的基礎(chǔ)上引入切面jar包
<!-- 切面所用到的jar包 -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/aspectj/aspectjrt -->
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
在springboot中新建一個(gè)TestController,在controller里聲明一個(gè)自定義注解
package com.zhaohy.app.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.zhaohy.app.service.TestService;
import com.zhaohy.app.sys.annotation.RecordLog;
@Controller
public class TestController {
@Autowired
TestService testService;
@RecordLog(module="moduleTest")
@RequestMapping("/test/test.do")
public void test(HttpServletRequest request) {
String userId = request.getParameter("userId");
try {
testService.test(userId);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
TestService:
package com.zhaohy.app.service;
public interface TestService {
public String doSearch(String userId, String keyword);
public void test(String userId) throws Exception;
}
TestServiceImpl:
package com.zhaohy.app.service.impl;
import org.springframework.stereotype.Service;
import com.zhaohy.app.service.TestService;
import com.zhaohy.app.sys.annotation.RecordLog;
@Service("TestService")
public class TestServiceImpl implements TestService {
public void test(String userId) throws Exception {
System.out.println(userId + "====執(zhí)行操作");
throw new Exception();
}
}
新建注解類:
package com.zhaohy.app.sys.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 日志記錄注解類
* @author zhaohy
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordLog {
/**
* 何種場(chǎng)景下的通用日志打印
*
* @return
*/
String module();
}
切面類:
package com.zhaohy.app.sys.annotation;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
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.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
/**
* 定義日志切面
* @author zhaohy
*@Lazy 注解:容器一般都會(huì)在啟動(dòng)的時(shí)候?qū)嵗袉螌?shí)例 bean,如果我們想要 Spring 在啟動(dòng)的時(shí)候延遲加載 bean丑瞧,需要用到這個(gè)注解
*value為true柑土、false 默認(rèn)為true,即延遲加載,@Lazy(false)表示對(duì)象會(huì)在初始化的時(shí)候創(chuàng)建
*/
@Aspect
@Component
@Lazy(false)
public class LoggerAspect {
/**
* 定義切入點(diǎn):對(duì)要攔截的方法進(jìn)行定義與限制绊汹,如包稽屏、類
*
* 1、execution(public * *(..)) 任意的公共方法
* 2西乖、execution(* set*(..)) 以set開頭的所有的方法
* 3狐榔、execution(* com.zhaohy.app.sys.annotation.LoggerApply.*(..))com.zhaohy.app.sys.annotation.LoggerApply這個(gè)類里的所有的方法
* 4、execution(* com.zhaohy.app.sys.annotation.*.*(..))com.zhaohy.app.sys.annotation包下的所有的類的所有的方法
* 5获雕、execution(* com.zhaohy.app.sys.annotation..*.*(..))com.zhaohy.app.sys.annotation包及子包下所有的類的所有的方法
* 6薄腻、execution(* com.zhaohy.app.sys.annotation..*.*(String,?,Long)) com.zhaohy.app.sys.annotation包及子包下所有的類的有三個(gè)參數(shù),第一個(gè)參數(shù)為String類型典鸡,第二個(gè)參數(shù)為任意類型被廓,第三個(gè)參數(shù)為L(zhǎng)ong類型的方法
* 7、execution(@annotation(com.zhaohy.app.sys.annotation.RecordLog))
*/
@Pointcut("@annotation(com.zhaohy.app.sys.annotation.RecordLog)")
private void cutMethod() {
}
/**
* 前置通知:在目標(biāo)方法執(zhí)行前調(diào)用
*/
@Before("cutMethod()")
public void begin() {
System.out.println("==@Before== begin");
}
/**
* 后置通知:在目標(biāo)方法執(zhí)行后調(diào)用萝玷,若目標(biāo)方法出現(xiàn)異常嫁乘,則不執(zhí)行
*/
@AfterReturning("cutMethod()")
public void afterReturning() {
System.out.println("==@AfterReturning== after returning");
}
/**
* 后置/最終通知:無(wú)論目標(biāo)方法在執(zhí)行過(guò)程中出現(xiàn)一場(chǎng)都會(huì)在它之后調(diào)用
*/
@After("cutMethod()")
public void after() {
System.out.println("==@After== finally returning");
}
/**
* 異常通知:目標(biāo)方法拋出異常時(shí)執(zhí)行
*/
@AfterThrowing("cutMethod()")
public void afterThrowing() {
System.out.println("==@AfterThrowing== after throwing");
}
/**
* 環(huán)繞通知:靈活自由的在目標(biāo)方法中切入代碼
*/
@Around("cutMethod()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
// 獲取目標(biāo)方法的名稱
String methodName = joinPoint.getSignature().getName();
// 獲取方法傳入?yún)?shù)
Object[] params = joinPoint.getArgs();
RecordLog recordLog = getDeclaredAnnotation(joinPoint);
HttpServletRequest request = (HttpServletRequest) params[0];
System.out.println("==@Around== --》 method name :'" + methodName + "' args :" + this.getRequestParams(request));
// 執(zhí)行源方法
joinPoint.proceed();
// 模擬進(jìn)行驗(yàn)證
if (params != null && params.length > 0 && params[0].equals("Blog Home")) {
System.out.println("==@Around== --》 " + recordLog.module() + " auth success");
} else {
System.out.println("==@Around== --》 " + recordLog.module() + " auth failed");
}
}
private String getRequestParams(HttpServletRequest request) {
Enumeration enu=request.getParameterNames();
Map<String, Object> paramsMap = new HashMap<String, Object>();
while(enu.hasMoreElements()){
String paraName=(String)enu.nextElement();
paramsMap.put(paraName, request.getParameter(paraName));
}
return JSON.toJSONString(paramsMap);
}
/**
* 獲取方法中聲明的注解
*
* @param joinPoint
* @return
* @throws NoSuchMethodException
*/
public RecordLog getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
// 獲取方法名
String methodName = joinPoint.getSignature().getName();
// 反射獲取目標(biāo)類
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法對(duì)應(yīng)的參數(shù)類型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根據(jù)類、方法球碉、參數(shù)類型(重載)獲取到方法的具體信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 拿到方法定義的注解信息
RecordLog annotation = objMethod.getDeclaredAnnotation(RecordLog.class);
// 返回
return annotation;
}
}
這樣代碼基本寫的差不多了蜓斧,瀏覽器訪問(wèn)`
http://127.0.0.1:8081/test/test.do?userId=楊過(guò)
控制臺(tái)打印結(jié)果:
==@Around== --》 method name :'test' args :{"userId":"楊過(guò)"}
==@Before== begin
楊過(guò)====執(zhí)行操作
java.lang.Exception
==@Around== --》 moduleTest auth failed
==@After== finally returning
==@AfterReturning== after returning
切面生效啦,如果在controller里不捕捉那個(gè)exception睁冬,則會(huì)觸發(fā)
/**
* 異常通知:目標(biāo)方法拋出異常時(shí)執(zhí)行
*/
@AfterThrowing("cutMethod()")
public void afterThrowing() {
System.out.println("==@AfterThrowing== after throwing");
}
總結(jié):
aop的思想類似過(guò)濾器挎春、攔截器等,都是縱向編程豆拨,在方法執(zhí)行前或者后或者前后或者拋出異常后等的后續(xù)處理操作直奋,也用到j(luò)ava的反射機(jī)制根據(jù)類路徑拿到類,拿到注解類的屬性施禾,拿到參數(shù)脚线,解析參數(shù),就可以做很多事情弥搞。