Spring AOP 使用與分析

[TOC]

AOP簡(jiǎn)介

理解

AOP(Aspect-Oriented Programming), 即 面向切面編程,其基本思想是在極少影響原程序的代碼的前提下,在程序中的某些地方,使用某些方式,不可見(jiàn)的(即不在原程序中添加其他代碼)為原程序切入一些額外的功能。

優(yōu)點(diǎn)

  • 減少代碼間的耦合性,使功能具有拔插性,保證自己代碼的清潔性。
  • 能夠讓你只關(guān)注自己的代碼,不需要關(guān)注切面是如何實(shí)現(xiàn)的。

術(shù)語(yǔ)

通知(advice)

其定義了切點(diǎn)什么時(shí)候去增強(qiáng),是在方法調(diào)用前,還是調(diào)用之后,還是前后都是,還是拋出異常時(shí)。

  • Before 某方法調(diào)用之前發(fā)出通知。
  • After 某方法完成之后發(fā)出通知,不考慮方法運(yùn)行的結(jié)果。
  • After-returning 將通知放置在被通知的方法成功執(zhí)行之后。
  • After-throwing 將通知放置在被通知的方法拋出異常之后。
  • Around 通知包裹在被通知的方法的周?chē)诜椒ㄕ{(diào)用之前和之后發(fā)出通知
連接點(diǎn)(join point)

可以被作為切點(diǎn)的地方,都可以被認(rèn)為是鏈接點(diǎn)。

切點(diǎn)(point cut)

按照規(guī)則被選中的鏈接點(diǎn),可以被稱(chēng)作為切點(diǎn)。

Aspect(切面)

aspectpointcountadvice 組成, 它既包含了橫切邏輯的定義, 也包括了連接點(diǎn)的定義. Spring AOP就是負(fù)責(zé)實(shí)施切面的框架, 它將切面所定義的橫切邏輯織入到切面所指定的連接點(diǎn)中.

目標(biāo)對(duì)象(Target)

織入 advice 后的目標(biāo)對(duì)象. 目標(biāo)對(duì)象也被稱(chēng)為 advised object.

引入(introductions):
  • 引入允許你添加一個(gè)新的方法給已經(jīng)存在的類(lèi)。

Spring對(duì)AOP的支持

  • Spring建議在Java中書(shū)寫(xiě)AOP
  • Spring是在運(yùn)行階段才將切面編織進(jìn)bean中,是使用代理類(lèi)。
  • Spring只支持方法級(jí)別的連接點(diǎn)。

AOP應(yīng)用

XML形式的AOP

proxy-target-class="true"指定使用GCLIB代理,如果proxy-target-class="false"或者沒(méi)設(shè)置,則默認(rèn)使用動(dòng)態(tài)代理,但是如果代理類(lèi)沒(méi)有實(shí)現(xiàn)接口,則依然會(huì)使用GCLIB代理。

aop:pointcut指定了切點(diǎn)。

aop:advisor指定了通知時(shí)機(jī),同樣的還有aop:before aop:after。

需要注意的是spiritCommonInterceptor實(shí)現(xiàn)了MethodInterceptor接口

   <bean id="spiritCommonInterceptor" class="com.mogujie.stable.spirit.point.methond.CommonInterceptor"/>
    <aop:config proxy-target-class="true">
        <aop:pointcut id="modulePoint" expression="@target(com.mogujie.stable.spirit.point.annotation.ClassSpirit) and @annotation(com.mogujie.stable.spirit.point.annotation.MethodSpirit)"/>
        <aop:advisor advice-ref="spiritCommonInterceptor" pointcut-ref="modulePoint"/>
    </aop:config>
public class CommonInterceptor implements MethodInterceptor {

    private static final Logger LOG = LoggerFactory.getLogger(CommonInterceptor.class);

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method executed = invocation.getMethod();
        Class<?> clazz = invocation.getThis().getClass();
        ClassSpirit classSpirit = clazz.getAnnotation(ClassSpirit.class);
        MethodSpirit methodSpirit = executed.getAnnotation(MethodSpirit.class);
        // 不做限流降級(jí)處理
        if (executed.getName().equals("toString") || executed.getName().equals("hashCode") || executed.getName().equals("equals") || (null != classSpirit && !classSpirit.trace()) || (null == classSpirit && null != methodSpirit && !methodSpirit.trace()) || ((null != classSpirit && classSpirit.trace()) && (null == methodSpirit || !methodSpirit.trace()))) {

            return invocation.proceed();
        }
        Entry entry = null;
        try {
            String methodName = MethodUtil.getMethodName(executed);

            // 初始化Context
            ContextUtil.enter(methodName);
            // 初始化Entry
            entry = EntryUtil.entry(executed);
            // 執(zhí)行方法
            Object result = invocation.proceed();
            return result;
        } catch (Throwable e) {
            throw ExceptionUtil.dealProxyException(e);
        } finally {
            if (entry != null) {
                entry.exit();
            }
            ContextUtil.exit();
        }
    }

}

Annotation形式的AOP

Spring除了支持Schema方式配置AOP,還支持注解方式:使用@Aspect來(lái)配置。但Spring默認(rèn)不支持@Aspect風(fēng)格的切面聲明,通過(guò)如下配置開(kāi)啟@Aspect支持:

<aop:aspectj-autoproxy/>  
package com.sxit;

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;

@Aspect
public class AspectStyle {
    
    @Pointcut("execution(* com.sxit..*.*(..))")
    public void init(){
        
    }

    @Before(value="init()")
    public void before(){
        System.out.println("方法執(zhí)行前執(zhí)行.....");
    }
    
    @AfterReturning(value="init()")
    public void afterReturning(){
        System.out.println("方法執(zhí)行完執(zhí)行.....");
    }
    
    @AfterThrowing(value="init()")
    public void throwss(){
        System.out.println("方法異常時(shí)執(zhí)行.....");
    }
    
    @After(value="init()")
    public void after(){
        System.out.println("方法最后執(zhí)行.....");
    }
    
    @Around(value="init()")
    public Object around(ProceedingJoinPoint pjp){
        System.out.println("方法環(huán)繞start.....");
        Object o = null;
        try {
            o = pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("方法環(huán)繞end.....");
        return o;
    }
}

一個(gè)Annotation與AOP結(jié)合的例子

要實(shí)現(xiàn)一個(gè)aop的功能,關(guān)鍵在于三個(gè)地方。

  • 通知(Advice) 定義了何時(shí)切。比如:before、around等。
  • 切點(diǎn)(PointCut) 定義了何處切。比如:execution(* com.mogujie.houston.openapi.api.impl..*(..))
  • 連接點(diǎn)(JoinPoint) 連接點(diǎn)是在應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn)。能夠利用它拿到應(yīng)用的方法和參數(shù)等。
    aspect
@Aspect
@Component
public class ValidatorAspect implements ApplicationContextAware {

    private static Logger logger = LoggerFactory.getLogger(ValidatorAspect.class);

    protected static ApplicationContext context;

    @Around("execution(* com.mogujie.houston.openapi.api.impl..*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            if (method.getDeclaringClass().isInterface()) {
                try {
                    method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
                            method.getParameterTypes());
                } catch (final SecurityException exception) {
                }
            }
            // check passport
            Object[] args = joinPoint.getArgs();
            Validator validatorClass = method.getAnnotation(Validator.class);
            if (null != validatorClass) {
                ValidationHandler validationHandler = validatorClass.handler().newInstance();
                HoustonOpenApiResult result = validationHandler.validate(args);
                if (!result.isSuccess()) {
                    return result;
                }
            }
            TokenValidator tokenValidatorClass = method.getAnnotation(TokenValidator.class);
            if (tokenValidatorClass != null) {
                TokenValidationHandler tokenValidationHandler = tokenValidatorClass.handler().newInstance();
                HoustonOpenApiResult result = tokenValidationHandler.check(args, context);
                if (!result.isSuccess()) {
                    return result;
                }
            }

        } catch (Exception e) {
            logger.error("ValidatorAspect驗(yàn)證出現(xiàn)異常", e);
            return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "系統(tǒng)異常 請(qǐng)@Houston答疑, error:" + e.getMessage());
        }
        try {
            return joinPoint.proceed();
        } catch (Exception e) {
            logger.error("Service服務(wù)出現(xiàn)異常", e);
            return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "Service出現(xiàn)異常 請(qǐng)@Houston答疑, error:" + e.getMessage());
        }

    }

    public static ApplicationContext getContext() {
        return context;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ValidatorAspect.context = applicationContext;
    }
}

@TokenValidator

@Documented
@Target({ElementType.METHOD})//只能在方法上使用
@Retention(RetentionPolicy.RUNTIME)//運(yùn)行時(shí)使用
public @interface TokenValidator {

    Class<? extends TokenValidationHandler> handler() default TokenValidationHandler.class;//定義了一個(gè)接口類(lèi)

}

TokenValidationHandler

public interface TokenValidationHandler {
    HoustonOpenApiResult check(Object[] args, ApplicationContext applicationContext);

    HoustonOpenApiResult getGroupResult(Object[] args, DefaultGroupBiz defaultGroupBiz);
}

一個(gè)Handler的實(shí)現(xiàn)

public abstract class BaseTVHandler implements TokenValidationHandler {


    @Override
    public HoustonOpenApiResult check(Object[] args, ApplicationContext context) {
        if (args.length >= 2) {
            Token token = (Token) args[0];
            DefaultGroupBiz defaultGroupBiz = context.getBean(DefaultGroupBiz.class);
            HoustonOpenApiResult<Group> groupResult = getGroupResult(args, defaultGroupBiz);
            if (!groupResult.isSuccess()) {
                return groupResult;
            }
            if (TokenUtil.check(token, groupResult.getData().getKeyName())) {
                return HoustonOpenApiResult.success(true);
            }
            return new HoustonOpenApiResult(OpenApiResultCode.TOKEN_ILLEGAL);
        } else {
            return new HoustonOpenApiResult(OpenApiResultCode.TOKEN_PARAM_ERROR);
        }
    }


}
public class ConfigValueTokenValidator {

    public static class DetailHandler extends BaseTVHandler {

        @Override
        public HoustonOpenApiResult getGroupResult(Object[] args, DefaultGroupBiz defaultGroupBiz) {
            ConfigValueDetail configValueDetail = (ConfigValueDetail) args[1];
            return defaultGroupBiz.queryByConfigId(configValueDetail.getConfigId());
        }

    }
}

AOP原理

Spring AOP使用了兩種代理機(jī)制:一種是基于JDK的動(dòng)態(tài)代理;另一種是基于CGLib的動(dòng)態(tài)代理。之所以需要兩種代理機(jī)制,很大程度上是因?yàn)镴DK本身只提供接口的代理,而不支持類(lèi)的代理。

JDK動(dòng)態(tài)代理

步驟

  1. 通過(guò)實(shí)現(xiàn)InvocationHandler接口創(chuàng)建自己的調(diào)用處理器
  2. 通過(guò)為Proxy類(lèi)指定ClassLoader對(duì)象和一組interface來(lái)創(chuàng)建動(dòng)態(tài)代理類(lèi)
  3. 通過(guò)反射機(jī)制獲得動(dòng)態(tài)代理類(lèi)的構(gòu)造函數(shù),其唯一參數(shù)類(lèi)型是調(diào)用處理器接口類(lèi)型
  4. 通過(guò)構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類(lèi)實(shí)例,構(gòu)造時(shí)調(diào)用處理器對(duì)象作為參數(shù)被傳入

public class DynamicTest implements InvocationHandler {


    private Test target;

    private DynamicTest(Test target) {
        this.target = target;
    }

    public static Test newProxyInstance(Test test) {
        return (Test) Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(), new DynamicTest(test));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }

}

基于CGLIB的動(dòng)態(tài)代理

CGLIB直接生成代理目標(biāo)類(lèi)的子類(lèi),不能對(duì)目標(biāo)類(lèi)中的final方法進(jìn)行代理。

  1. 查找A上的所有非final 的public類(lèi)型的方法定義;

  2. 將這些方法的定義轉(zhuǎn)換成字節(jié)碼;

  3. 將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的class對(duì)象;

  4. 實(shí)現(xiàn) MethodInterceptor接口,用來(lái)處理 對(duì)代理類(lèi)上所有方法的請(qǐng)求(這個(gè)接口和JDK動(dòng)態(tài)代理InvocationHandler的功能和角色是一樣的)

public class CglibTest implements MethodInterceptor {

    private CglibTest() {
    }

    public static <T extends Test> Test newProxyInstance(Class<T> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(new CglibTest());
        return (Test) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(o, objects);
    }

}

ASM(介紹)

ASM 是一個(gè) Java 字節(jié)碼操控框架。它能夠以二進(jìn)制形式修改已有類(lèi)或者動(dòng)態(tài)生成類(lèi)。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件,也可以在類(lèi)被加載入 Java 虛擬機(jī)之前動(dòng)態(tài)改變類(lèi)行為。ASM 從類(lèi)文件中讀入信息后,能夠改變類(lèi)行為,分析類(lèi)信息,甚至能夠根據(jù)用戶要求生成新類(lèi)。

不過(guò)ASM在創(chuàng)建class字節(jié)碼的過(guò)程中,操縱的級(jí)別是底層JVM的匯編指令級(jí)別,這要求ASM使用者要對(duì)class組織結(jié)構(gòu)和JVM匯編指令有一定的了解。

Javassist(介紹)

Javassist是一款字節(jié)碼編輯工具,可以直接編輯和生成Java生成的字節(jié)碼,以達(dá)到對(duì).class文件進(jìn)行動(dòng)態(tài)修改的效果。熟練使用這套工具,可以讓Java編程更接近與動(dòng)態(tài)語(yǔ)言編程。

JDK動(dòng)態(tài)代理與CGLIB性能比較

  1. 被代理接口
public interface Test {

    public int test(int i);
}

  1. 實(shí)現(xiàn)類(lèi)
public class TestImpl implements Test {
    @Override
    public int test(int i) {
        return i + 1;
    }

    public void print() {
        System.out.println("111111");
    }

}
  1. JDK代理類(lèi)
public class DynamicTest implements InvocationHandler {


    private Test target;

    private DynamicTest(Test target) {
        this.target = target;
    }

    public static Test newProxyInstance(Test test) {
        return (Test) Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(), new DynamicTest(test));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }

}
  1. CGLIB代理類(lèi)
public class CglibTest implements MethodInterceptor {

    private CglibTest() {
    }

    public static <T extends Test> Test newProxyInstance(Class<T> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(new CglibTest());
        return (Test) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(o, objects);
    }
}
  1. 測(cè)試類(lèi)
public class ProxyPerfTester {

    public static void main(String[] args) {
        //創(chuàng)建測(cè)試對(duì)象;
        Test nativeTest = new TestImpl();
        Test dynamicProxy = DynamicTest.newProxyInstance(nativeTest);
        Test cglibProxy = CglibTest.newProxyInstance(TestImpl.class);

        //預(yù)熱一下;
        int preRunCount = 100000;
        runWithoutMonitor(nativeTest, preRunCount);
        runWithoutMonitor(cglibProxy, preRunCount);
        runWithoutMonitor(dynamicProxy, preRunCount);

        //執(zhí)行測(cè)試;
        Map<String, Test> tests = new LinkedHashMap<String, Test>();
        tests.put("Native   ", nativeTest);
        tests.put("Dynamic  ", dynamicProxy);
        tests.put("Cglib    ", cglibProxy);
        int repeatCount = 3;
        int runCount = 1000000;
        runTest(repeatCount, runCount, tests);
        runCount = 50000000;
        runTest(repeatCount, runCount, tests);
    }

    private static void runTest(int repeatCount, int runCount, Map<String, Test> tests){
        System.out.println(String.format("\n==================== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] ====================", repeatCount, runCount, System.getProperty("java.version")));
        for (int i = 0; i < repeatCount; i++) {
            System.out.println(String.format("\n--------- test : [%s] ---------", (i+1)));
            for (String key : tests.keySet()) {
                runWithMonitor(tests.get(key), runCount, key);
            }
        }
    }

    private static void runWithoutMonitor(Test test, int runCount) {
        for (int i = 0; i < runCount; i++) {
            test.test(i);
        }
    }

    private static void runWithMonitor(Test test, int runCount, String tag) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < runCount; i++) {
            test.test(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("["+tag + "] Elapsed Time:" + (end-start) + "ms");
    }
}
  1. 結(jié)果
Create Native Proxy:1ms
Create Dynamic Proxy17ms
Create Cglib Proxy521ms

==================== run test : [repeatCount=3] [runCount=1000000] [java.version=1.7.0_79] ====================

--------- test : [1] ---------
[Native   ] Elapsed Time:7ms
[Dynamic  ] Elapsed Time:289ms
[Cglib    ] Elapsed Time:93ms

--------- test : [2] ---------
[Native   ] Elapsed Time:7ms
[Dynamic  ] Elapsed Time:12ms
[Cglib    ] Elapsed Time:51ms

--------- test : [3] ---------
[Native   ] Elapsed Time:6ms
[Dynamic  ] Elapsed Time:14ms
[Cglib    ] Elapsed Time:45ms

==================== run test : [repeatCount=3] [runCount=50000000] [java.version=1.7.0_79] ====================

--------- test : [1] ---------
[Native   ] Elapsed Time:468ms
[Dynamic  ] Elapsed Time:1855ms
[Cglib    ] Elapsed Time:1577ms

--------- test : [2] ---------
[Native   ] Elapsed Time:165ms
[Dynamic  ] Elapsed Time:418ms
[Cglib    ] Elapsed Time:807ms

--------- test : [3] ---------
[Native   ] Elapsed Time:161ms
[Dynamic  ] Elapsed Time:484ms
[Cglib    ] Elapsed Time:889ms

可見(jiàn)在JDK1.7下:

  • 運(yùn)行速度,Native是最快的,JDK動(dòng)態(tài)代理稍次之,CGLIB最慢。
  • 創(chuàng)建速度,Native是最快的,JDK動(dòng)態(tài)代理稍次之,CGLIB最慢。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容