AOP稱爲面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,好比日誌,事務,權限等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。java
一 AOP的基本概念spring
(1)Aspect(切面):一般是一個類,裏面能夠定義切入點和通知編程
(2)JointPoint(鏈接點):程序執行過程當中明確的點,通常是方法的調用app
(3)Advice(通知):AOP在特定的切入點上執行的加強處理,有before,after,afterReturning,afterThrowing,around框架
(4)Pointcut(切入點):就是帶有通知的鏈接點,在程序中主要體現爲書寫切入點表達式ssh
(5)AOP代理:AOP框架建立的對象,代理就是目標對象的增強。Spring中的AOP代理可使JDK動態代理,也能夠是CGLIB代理,前者基於接口,後者基於子類分佈式
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。微服務
二 Spring AOP源碼分析
Spring中的AOP代理仍是離不開Spring的IOC容器,代理的生成,管理及其依賴關係都是由IOC容器負責,Spring默認使用JDK動態代理,在須要代理類而不是代理接口的時候,Spring會自動切換爲使用CGLIB代理,不過如今的項目都是面向接口編程,因此JDK動態代理相對來講用的仍是多一些。性能
三 基於註解的AOP配置方式
1.啓用@AsjectJ支持
在applicationContext.xml中配置下面一句:
<aop:aspectj-autoproxy />
2.通知類型介紹
(1)Before:在目標方法被調用以前作加強處理,@Before只須要指定切入點表達式便可
(2)AfterReturning:在目標方法正常完成後作加強,@AfterReturning除了指定切入點表達式後,還能夠指定一個返回值形參名returning,表明目標方法的返回值
(3)AfterThrowing:主要用來處理程序中未處理的異常,@AfterThrowing除了指定切入點表達式後,還能夠指定一個throwing的返回值形參名,能夠經過該形參名
來訪問目標方法中所拋出的異常對象
(4)After:在目標方法完成以後作加強,不管目標方法時候成功完成。@After能夠指定一個切入點表達式
(5)Around:環繞通知,在目標方法完成先後作加強處理,環繞通知是最重要的通知類型,像事務,日誌等都是環繞通知,注意編程中核心是一個ProceedingJoinPoint
3.例子:
(1)Operator.java --> 切面類
@Component @Aspect public class Operator { @Pointcut("execution(* com.aijava.springcode.service..*.*(..))") public void pointCut(){} @Before("pointCut()") public void doBefore(JoinPoint joinPoint){ System.out.println("AOP Before Advice..."); } @After("pointCut()") public void doAfter(JoinPoint joinPoint){ System.out.println("AOP After Advice..."); } @AfterReturning(pointcut="pointCut()",returning="returnVal") public void afterReturn(JoinPoint joinPoint,Object returnVal){ System.out.println("AOP AfterReturning Advice:" + returnVal); } @AfterThrowing(pointcut="pointCut()",throwing="error") public void afterThrowing(JoinPoint joinPoint,Throwable error){ System.out.println("AOP AfterThrowing Advice..." + error); System.out.println("AfterThrowing..."); } @Around("pointCut()") public void around(ProceedingJoinPoint pjp){ System.out.println("AOP Aronud before..."); try { pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("AOP Aronud after..."); } }
(2)UserService.java --> 定義一些目標方法
@Service public class UserService { public void add(){ System.out.println("UserService add()"); } public boolean delete(){ System.out.println("UserService delete()"); return true; } public void edit(){ System.out.println("UserService edit()"); int i = 5/0; } }
(3).applicationContext.xml
<context:component-scan base-package="com.aijava.springcode"/> <aop:aspectj-autoproxy />
(4).Test.java
public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.add(); } }
上面是一個比較簡單的測試,基本涵蓋了各類加強定義。注意:作環繞通知的時候,調用ProceedingJoinPoint的proceed()方法纔會執行目標方法。
4.通知執行的優先級
進入目標方法時,先織入Around,再織入Before,退出目標方法時,先織入Around,再織入AfterReturning,最後才織入After。
注意:Spring AOP的環繞通知會影響到AfterThrowing通知的運行,不要同時使用!同時使用也沒啥意義。
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
5.切入點的定義和表達式
切入點表達式的定義算是整個AOP中的核心,有一套本身的規範
Spring AOP支持的切入點指示符:
(1)execution:用來匹配執行方法的鏈接點
A:@Pointcut("execution(* com.aijava.springcode.service..*.*(..))")
第一個*表示匹配任意的方法返回值,..(兩個點)表示零個或多個,上面的第一個..表示service包及其子包,第二個*表示全部類,第三個*表示全部方法,第二個..表示
方法的任意參數個數
B:@Pointcut("within(com.aijava.springcode.service.*)")
within限定匹配方法的鏈接點,上面的就是表示匹配service包下的任意鏈接點
C:@Pointcut("this(com.aijava.springcode.service.UserService)")
this用來限定AOP代理必須是指定類型的實例,如上,指定了一個特定的實例,就是UserService
D:@Pointcut("bean(userService)")
bean也是很是經常使用的,bean能夠指定IOC容器中的bean的名稱
後言: spring 的環繞通知和前置通知,後置通知有着很大的區別,主要有兩個重要的區別:
1) 目標方法的調用由環繞通知決定,即你能夠決定是否調用目標方法,而前置和後置通知 是不能決定的,他們只是在方法的調用先後執行通知而已,即目標方法確定是要執行的。
2) 環繞通知能夠控制返回對象,即你能夠返回一個與目標對象徹底不一樣的返回值,雖然這很危險,可是你卻能夠辦到。然後置方法是沒法辦到的,由於他是在目標方法返回值後調用
6.基於XML形式的配置方式
開發中若是選用XML配置方式,一般就是POJO+XML來開發AOP,大同小異,無非就是在XML文件中寫切入點表達式和通知類型
例子:
(1)Log.java
public class Log { private Integer id; //操做名稱,方法名 private String operName; //操做人 private String operator; //操做參數 private String operParams; //操做結果 成功/失敗 private String operResult; //結果消息 private String resultMsg; //操做時間 private Date operTime = new Date(); setter,getter }
(2).Logger.java
/** * 日誌記錄器 (AOP日誌通知) */ public class Logger { @Resource private LogService logService; public Object record(ProceedingJoinPoint pjp){ Log log = new Log(); try { log.setOperator("admin"); String mname = pjp.getSignature().getName(); log.setOperName(mname); //方法參數,本例中是User user Object[] args = pjp.getArgs(); log.setOperParams(Arrays.toString(args)); //執行目標方法,返回的是目標方法的返回值,本例中 void Object obj = pjp.proceed(); if(obj != null){ log.setResultMsg(obj.toString()); }else{ log.setResultMsg(null); } log.setOperResult("success"); log.setOperTime(new Date()); return obj; } catch (Throwable e) { log.setOperResult("failure"); log.setResultMsg(e.getMessage()); } finally{ logService.saveLog(log); } return null; } }
(3).applicationContext.xml
<aop:config> <aop:aspect id="loggerAspect" ref="logger"> <aop:around method="record" pointcut="(execution(* com.aijava.distributed.ssh.service..*.add*(..)) or execution(* com.aijava.distributed.ssh.service..*.update*(..)) or execution(* com.aijava.distributed.ssh.service..*.delete*(..))) and !bean(logService)"/> </aop:aspect> </aop:config>
注意切入點表達式,!bean(logService) 作日誌通知的時候,不要給日誌自己作日誌,不然會形成無限循環!
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
有關更詳細的Spring AOP知識,能夠查看Spring官方文檔第9章Aspect Oriented Programming with Spring
7.JDK動態代理介紹
例子:
(1)UserService.java
public interface UserService { public void add(); }
(2)UserServiceImpl.java
public class UserServiceImpl implements UserService{ public void add() { System.out.println("User add()..."); } }
(3)ProxyUtils.java
public class ProxyUtils implements InvocationHandler{ private Object target; public ProxyUtils(Object target){ this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do sth before..."); method.invoke(target, args); System.out.println("do sth after..."); return null; } }
(4)Test.java
public class Test { public static void main(String[] args) { UserService userService = new UserServiceImpl(); ProxyUtils proxyUtils = new ProxyUtils(userService); UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),UserServiceImpl.class.getInterfaces(), proxyUtils); proxyObject.add(); } }
JDK動態代理核心仍是一個InvocationHandler,記住這個就好了。