寫了不少篇文章了,但寫的文章你們都反映平平(但我卻感受是本身的嘔心瀝血之做),是時候改變一下寫做技巧了,但願能經過一種愉快的方式使你們學到最多的知識。
之前寫的文章直接上源碼分析,這會讓不瞭解的人看着很累,得不到想要的效果。本篇文章則從背景-原理-使用-源碼的順序爲你們解析。若是不但願深刻了解,看到「使用」這個層次就足夠了。但願你們能愉快地看完這篇文章,多少給點反饋唄javascript
「存在即合理」,任何一種理論或技術的產生,必然有它的緣由。瞭解它產生的背景、爲了解決的問題有助於咱們更好地把握AOP的概念。
軟件開發一直在尋求一種高效開發、護展、維護的方式。從面向過程的開發實踐中,前人將關注點抽象出來,對行爲和屬性進行聚合,造成了面向對象的開發思想,其在必定程度上影響了軟件開發的過程。鑑於此,咱們在開發的過程當中會對軟件開發進行抽象、分割成各個模塊或對象。例如,咱們會對API進行抽象成四個模塊:Controller,Service,Gateway,Command.這很好地解決了業務級別的開發,但對於系統級別的開發咱們很難聚焦。好比、對於每個模塊須要進行打日誌、代碼監控、異常處理。以打日誌爲例,我只能將日誌代碼嵌套在各個對象上,而沒法關注日誌自己,而這種現象又偏離了OOP思想。html
即在哪一個地方進行切入,它能夠指定某一個點,也能夠指定多個點。
好比類A的methord函數,固然通常的AOP與語言(AOL)會採用多用方式來定義PointCut,好比說利用正則表達式,能夠同時指定多個類的多個函數。java
在切入點幹什麼,指定在PointCut地方作什麼事情(加強),打日誌、執行緩存、處理異常等等。git
PointCut + Advice 造成了切面Aspect,這個概念自己即表明切面的全部元素。但到這一地步並非完整的,由於還不知道如何將切面植入到代碼中,解決此問題的技術就是PROXYgithub
Proxy 即代理,其不能算作AOP的家庭成員,更至關於一個管理部門,它管理 了AOP的如何融入OOP。之因此將其放在這裏,是由於Aspect雖然是面向切面核心思想的重要組成部分,但其思想的踐行者倒是Proxy,也是實現AOP的難點與核心據在。正則表達式
AOP僅僅是一種思想,那爲了讓這種思想發光,必然脫離語言自己的技術支持,Java在實現該技術時就是採用的代理Proxy,那咱們就去了解一下,如何經過代理實現面向切面spring
public interface IPerson {
public void doSomething();
}複製代碼
public class Person implements IPerson {
public void doSomething(){
System.out.println("I want wo sell this house");
}
}複製代碼
public class PersonProxy {
private IPerson iPerson;
private final static Logger logger = LoggerFactory.getLogger(PersonProxy.class);
public PersonProxy(IPerson iPerson) {
this.iPerson = iPerson;
}
public void doSomething() {
logger.info("Before Proxy");
iPerson.doSomething();
logger.info("After Proxy");
}
public static void main(String[] args) {
PersonProxy personProxy = new PersonProxy(new Person());
personProxy.doSomething();
}
}複製代碼
經過代理類咱們實現了將日誌代碼集成到了目標類,但從上面咱們能夠看出它具備很大的侷限性:須要固定的類編寫接口(或許還能夠接受,畢竟有提倡面向接口編程),須要實現接口的每個函數(不可接受),一樣會形成代碼的大量重複,將會使代碼更加混亂。編程
那可否經過實現一次代碼便可將logger織入到全部函數中呢,答案固然是能夠的,此時就要用到java中的反射機制緩存
public class PersonProxy implements InvocationHandler{
private Object delegate;
private final Logger logger = LoggerFactory.getLogger(this.getClass();
public Object bind(Object delegate) {
this.delegate = delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
logger.info("Before Proxy");
result = method.invoke(delegate, args);
logger.info("After Proxy");
} catch (Exception e) {
throw e;
}
return result;
}
public static void main(String[] args) {
PersonProxy personProxy = new PersonProxy();
IPerson iperson = (IPerson) personProxy.bind(new Person());
iperson.doSomething();
}
}複製代碼
它的好處理時能夠爲咱們生成任何一個接口的代理類,並將須要加強的方法織入到任意目標函數。但它仍然具備一個侷限性,就是隻有實現了接口的類,才能爲其實現代理。性能優化
CGLIB解決了動態代理的難題,它經過生成目標類子類的方式來實現來實現代理,而不是接口,規避了接口的侷限性。
CGLIB是一個強大的高性能代碼生成包(生成原理還沒研究過),其在運行時期(非編譯時期)生成被 代理對象的子類,並重寫了被代理對象的全部方法,從而做爲代理對象。
public class PersonProxy implements MethodInterceptor {
private Object delegate;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
logger.info("Before Proxy");
Object result = methodProxy.invokeSuper(method, args);
logger.info("After Proxy");
return result;
}
public static Person getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new PersonProxy());
return (Person) enhancer.create();
}
}複製代碼
固然CGLIB也具備侷限性,對於沒法生成子類的類(final類),確定是沒有辦法生成代理子類的。
以上就是三種代理的實現方式,但千成別被迷惑了,在Spring AOP中這些東西已經被封裝了,不須要咱們本身實現。要否則得累死,但瞭解AOP的實現原理(即基於代理)仍是頗有必要的。
先建一個目標類(這個類我本身都噁心),仍是賣房子的事,討論價格,直接寫死了,意思意思……
public class Person {
public int tradePrice () {
return 1000;
}
}複製代碼
繼承MethodInteceptor來實現一個Advisor,固然可選擇的有很多,下面都有介紹,也在這裏意思意思……
public class LogsInterceptor implements MethodInterceptor {
Logger logger = LoggerFactory.getLogger(this.getClass().getName());
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
logger.info("Start bargaining....");
Object returnValue = invocation.proceed();
} catch (Exception e) {
throw e;
} finally {
logger.info("Bargaining Over");
}
return null;
}
}複製代碼
一個個地配置就能夠了,這樣雖然麻煩,可是你知道原理呀……
<bean id="pointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="tradePrice"/> </bean>
<bean id="myInterceptor" class="com.sankuai.meituan.meishi.poi.tag.LogsInterceptor"></bean>
<bean id="myAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointCut"/> <property name="advice" ref="myInterceptor"/> </bean> <bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interceptorNames"> <list> <value>myAdvisor</value> </list> </property> </bean>複製代碼
但上面這個噁心的地方在於,你得一個個地指定義advisor,有幾個目標pointCut,你就得定義幾回,這還不得噁心死
爲了解決上面的問題AutoProxyCreator 有兩個具體的實現類BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator,先拿一個練練手
<bean id="pointCut1" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="tradePrice"/>
</bean>
<bean id="pointCut2" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="tradePrice"/>
</bean>
<bean id="myInterceptor" class="com.sankuai.meituan.meishi.poi.tag.LogsInterceptor"></bean>
<bean id="myProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>pointCut1</value>
<value>pointCut2</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>複製代碼
是否是感受好多了,但你們都不喜歡寫這麼東西(不過我仍是推薦採用這種配置的)
引入aspectj就可用aspect的註解來實現了,這個纔是只關注切面自己就能夠了
public class MyAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(public void *.method1)")
public void pointcutName(){}
@Around("pointcutName()")
public Object performanceTrace(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
logger.info("log.....");
return proceedingJoinPoint.proceed();
} finally {
logger.info("log end");
}
}
}複製代碼
切面的代理生成就靠它了……
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>複製代碼
aspectj的功能是很是強大的,其定義語法和運算這裏就再也不深刻了,不是本文的重點
Spring AOP是AOL家庭Loser 也是winner,遵循大道至簡。Spring AOP只支持部分AOP的功能,做爲一個輕量級框架,實現了AOP20%的技術,支撐80%的需求,雖然對AspectJ進行了集成,但其內部原理仍然使用是是Spring AOP,因此也只能使用AspectJ的部分功能。
如下內容僅侷限於Spring Aop,而不包括其它AOL(Spring Aop相對於其它AOL功能簡單的多,但也預留了對其它AOL的支持)
public abstract class StaticMethodMatcher implements MethodMatcher {
@Override
public final boolean isRuntime() {
return false;
}
@Override
public final boolean matches(Method method, Class<?> targetClass, Object[] args) {
// should never be invoked because isRuntime() returns false
throw new UnsupportedOperationException("Illegal MethodMatcher usage");
}
}複製代碼
public abstract class DynamicMethodMatcher implements MethodMatcher {
@Override
public final boolean isRuntime() {
return true;
}
/** * Can override to add preconditions for dynamic matching. This implementation * always returns true. */
@Override
public boolean matches(Method method, Class<?> targetClass) {
return true;
}
}複製代碼
相似繼承於StaticMethodMatcher和DynamicMethodMatcher也有兩個分支StaticMethodMatcherPointcut和DynamicMethodMatcherPointcut,StaticMethodMatcherPointcut是咱們最經常使用,其具體實現有兩個NameMatchMethodPointcut和JdkRegexpMethodPointcut,一個經過name進行匹配,一個經過正則表達式匹配。
有必要對另一個分支說一下ExpressionPointcut,它的出現是了對AspectJ的支持,因此其具體實現也有AspectJExpressionPointcut
最左邊的三個給咱們提供了三個更強功能的PointCut
AnnotationMatchingPointcut:能夠指定某種類型的註解
ComposiblePointcut:進行與或操做
ControlFlowPointcut:這個有些特殊,它是一種控制流,例如類A 調用B.method(),它能夠指定當被A調用時才進行攔截。
咱們來看一下Advice 的類圖,先看一下接口的分類:
一樣Advisor按照Advice去分也能夠分紅兩條線路,一個是來源於Spring AOP 的類型,一種是來自AOP聯盟的Interceptoor, IntroductionAdvisor就是對MethodInterceptor的繼承和實現
AbstractPointcutAdvisor 實現了Ordered,爲多個Advice指定順序,順序爲Int類型,越小優先級越高,
AbstractGenericPointcutAdvisor 指定了Advice,除了Introduction以外的類型
下面具體的Advisor實現則對應於PointCut 的類型,具體指定哪一個pointCut,
Introduction類型,與上面的基本相似,再也不介紹了
這一節才最關鍵,它決定了如何具體實現AOP,因此這一節也將會難理解一些,
先看一下類圖,看起來也挺簡單
private boolean proxyTargetClass = false;
private boolean optimize = false;
boolean opaque = false;
boolean exposeProxy = false;
private boolean frozen = false;複製代碼
private boolean proxyTargetClass = false;
代理有兩種方式:一種是接口代理(上文提到過的動態代理),一種是CGLIB。默認有接口的類採用接口代理,不然使用CGLIB。若是設置成true,則直接使用CGLIB;
原文註釋以下
/** * Set whether to proxy the target class directly, instead of just proxying * specific interfaces. Default is "false". * <p>Set this to "true" to force proxying for the TargetSource's exposed * target class. If that target class is an interface, a JDK proxy will be * created for the given interface. If that target class is any other class, * a CGLIB proxy will be created for the given class. * <p>Note: Depending on the configuration of the concrete proxy factory, * the proxy-target-class behavior will also be applied if no interfaces * have been specified (and no interface autodetection is activated). * @see org.springframework.aop.TargetSource#getTargetClass() */複製代碼
private boolean optimize = false;是否進行優化,不一樣代理的優化通常是不一樣的。如代理對象生成以後,就會忽略Advised的變更。
/** * Set whether proxies should perform aggressive optimizations. * The exact meaning of "aggressive optimizations" will differ * between proxies, but there is usually some tradeoff. * Default is "false". * <p>For example, optimization will usually mean that advice changes won't * take effect after a proxy has been created. For this reason, optimization * is disabled by default. An optimize value of "true" may be ignored * if other settings preclude optimization: for example, if "exposeProxy" * is set to "true" and that's not compatible with the optimization. */複製代碼
opaque 是否強制轉化爲advised
/** * Set whether proxies created by this configuration should be prevented * from being cast to {@link Advised} to query proxy status. * <p>Default is "false", meaning that any AOP proxy can be cast to * {@link Advised}. */複製代碼
exposeProxy:AOP生成對象時,綁定到ThreadLocal, 能夠經過AopContext獲取
/** * Set whether the proxy should be exposed by the AOP framework as a * ThreadLocal for retrieval via the AopContext class. This is useful * if an advised object needs to call another advised method on itself. * (If it uses {@code this}, the invocation will not be advised). * <p>Default is "false", in order to avoid unnecessary extra interception. * This means that no guarantees are provided that AopContext access will * work consistently within any method of the advised object. */複製代碼
frozen:代理信息一旦設置,是否容許改變
/** * Set whether this config should be frozen. * <p>When a config is frozen, no advice changes can be made. This is * useful for optimization, and useful when we don't want callers to * be able to manipulate configuration after casting to Advised. */複製代碼
AdvisedSupport則的做用是設置生成代理對象所須要的所有信息。
ProxyCreatorSupport則完成生成代理的相關工做。
至於它們兩個具體作了哪些工做,這裏不展開講了,由於內容不少,不一樣的advisor和pointCut所進行的操做封裝有所不一樣, 在使用到的時候再仔細看源碼,很容易看懂。
那何時來使用AOP呢?我就私自定義一下:當一個功能和對象自己沒有必段關係,重複出如今多個模塊時,就應該用AOP來解耦。
有人總結了一下使用情景(參考, 是否定同本身決定):
Authentication 權限
Caching 緩存
Context passing 內容傳遞
Error handling 錯誤處理
Lazy loading 懶加載
Debugging 調試
logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準
Performance optimization 性能優化
Persistence 持久化
Resource pooling 資源池
Synchronization 同步
Transactions 事務
看了不少資料都是用日誌作AOP的例子,其實這個並不太好,由於AOP很難徹底實現log的行爲,但對於某一類型的日誌處理仍是有用的。
例如:對Command的異常進行統一處理,對Controller層的請求進行打日誌
請參考日誌:github.com/ameizi/DevA…
利用PerformanceMonitorInterceptor來協助應用性能優化, spring自帶的
www.cnblogs.com/f1194361820…固然還有這個JamonPerformanceMonitorInterceptor