Spring的最終目的是簡化應用開發。通俗的講減小重複代碼,少寫代碼達到相同的目的。面向切面編程(AOP, Aspect Oriented Programming)就是一種減重複代碼方式。咱們都知道JAVA是一門面向對象編程(OOP, Object Oriented Programming)語言,在java中將一個個功能模塊抽象成一個個對象。這些對象經過必定的聯繫完成咱們所看到的一個個應用,一個個服務。它的核心就是對象(Object)。html
要理解切面編程,就須要先理解什麼是切面。用刀把一個西瓜分紅兩瓣,切開的切口就是切面;炒菜,鍋與爐子共同來完成炒菜,鍋與爐子就是切面。web層級設計中,web層->網關層->服務層->數據層,每一層之間也是一個切面。編程中,對象與對象之間,方法與方法之間,模塊與模塊之間都是一個個切面。java
類應該是純淨的,不該含有與自己無關的邏輯。單一職責原則。程序員
切面編程,能夠帶來代碼的解耦;同時,切面編程也是須要執行代碼的,增長了一些額外的代碼執行量。因地制宜,使用AOP。web
咱們通常作活動的時候,通常對每個接口都會作活動的有效性校驗(是否開始、是否結束等等)、以及這個接口是否是須要用戶登陸。spring
按照正常的邏輯,咱們能夠這麼作。編程
這有個問題就是,有多少接口,就要多少次代碼copy。對於一個「懶人」,這是不可容忍的。好,提出一個公共方法,每一個接口都來調用這個接口。這裏有點切面的味道了。json
一樣有個問題,我雖然不用每次都copy代碼了,可是,每一個接口總得要調用這個方法吧。因而就有了切面的概念,我將方法注入到接口調用的某個地方(切點)。緩存
這樣接口只須要關心具體的業務,而不須要關注其餘非該接口關注的邏輯或處理。markdown
紅框處,就是面向切面編程。app
使用AOP以前,咱們須要理解幾個概念。
全部可能的須要注入切面的地方。如方法先後、類初始化、屬性初始化先後等等。
須要作某些處理(如打印日誌、處理緩存等等)的鏈接點。如何來指明一個切點?spring使用了AspectJ的切點表達式語言來定義Spring切面。
多個表達式之間,可用「and」 、「or」、「not」作邏輯鏈接。
其中較爲複雜的是execution。
目前,Spring只支持方法的切點定義
定義在何時作什麼事情。spring支持5種方法上的通知類型
通知+切點的集合,定義在什麼地方什麼時間作什麼事情。
容許咱們向現有的類添加新方法屬性。這不就是把切面(也就是新方法屬性:通知定義的)用到目標類中嗎
引入中提到的目標類,也就是要被通知的對象。也就是真正的業務邏輯,他能夠在絕不知情的狀況下,被我們織入切面。而本身專一於業務自己的邏輯。
把切面應用到目標對象來建立新的代理對象的過程。
AOP的實現其實是用的是代理模式。
代理概念:簡單的理解就是經過爲某一個對象建立一個代理對象,咱們不直接引用本來的對象,而是由建立的代理對象來控制對原對象的引用。
按照代理的建立時期,代理類能夠分爲兩種。
代理原理:代理對象內部含有對真實對象的引用,從而能夠操做真實對象,同時代理對象提供與真實對象相同的接口以便在任什麼時候刻都能代替真實對象。同時,代理對象能夠在執行真實對象操做時,附加其餘的操做,至關於對真實對象進行封裝。
spring使用的是動態代理。
java jdk 自己支持動態建立代理對象。
java的動態代理,有個缺點,它只支持實現了接口的類。所以能夠引入cglib(Code Generation Library)三方庫支持類的代理。 對JDK動態代理有興趣的能夠參考一下:blog.csdn.net/u012410733/…
對cglib庫有興趣能夠參考一下:blog.csdn.net/danchu/arti…
有這麼一個需求,對某些接口作緩存或打印日誌。有不想每一個接口中都調用緩存方法或打印日誌方法。能夠這麼來作。 代碼:
package bean; public interface Animal { public String sayName(String name, Integer num); } 複製代碼
package bean; import org.springframework.stereotype.Component; @Component public class Cat implements Animal { @Cache(60) public String sayName(String name, Integer num) { return "this is cat " + name + "," + num; } } 複製代碼
package bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan @EnableAspectJAutoProxy(proxyTargetClass = true) public class AnimalConfig { } 複製代碼
package bean; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cache { int value() default 0; } 複製代碼
package bean; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; 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.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Map; @Component @Aspect @Order(1000) public class CacheAspect { // 定義切入點:帶有Cache註解的方法 @Pointcut("@annotation(Cache)") private void cache(){} // 臨時存儲區 private static Map<String, Object> cacheList = new LinkedHashMap<String, Object>(); // 定義環繞通知,處理接口/方法添加緩存 @Around("cache()") private Object cacheAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ Object object = proceedingJoinPoint.getTarget(); Object[] args = proceedingJoinPoint.getArgs(); String className = object.getClass().getName(); MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature(); Method method = signature.getMethod(); // 組裝cache key String cacheKey = className + "_" + method.getName() + "_" + JSONObject.toJSONString(args); if (cacheList.containsKey(cacheKey)){ System.out.println("data get cache"); return cacheList.get(cacheKey); }else { System.out.println("data get db"); Object result = proceedingJoinPoint.proceed(); cacheList.put(cacheKey, result); return result; } } } 複製代碼
package bean; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Aspect @Order(100) public class LogAspect { //定義切點:包含任意參數的、任意返回值、的公共方法sayName @Pointcut("execution(public * *sayName(..))") private void log(){} //定義環繞通知:處理日誌注入 @Around("log()") private Object logAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object[] args = proceedingJoinPoint.getArgs(); System.out.println("before, params:" + JSONObject.toJSONString(args)); Object result = proceedingJoinPoint.proceed(); System.out.println("after, result:" + JSONObject.toJSONString(result)); return result; } } 複製代碼
import bean.Animal; import bean.AnimalConfig; import bean.Cat; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class App { public static void main(String[] args){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnimalConfig.class); Animal animal = applicationContext.getBean("cat", Cat.class); String result = animal.sayName("rudytan", 12); String result1 = animal.sayName("rudytan", 12); String result2 = animal.sayName("rudytan", 12); String result3 = animal.sayName("rudytan", 13); String result4 = animal.sayName("rudytan", 13); System.out.println(result); } } 複製代碼
說明: