美文网首页
Spring AOP 使用与分析

Spring AOP 使用与分析

作者: Wang_Coder | 来源:发表于2017-10-28 15:08 被阅读30次

    [TOC]

    AOP简介

    理解

    AOP(Aspect-Oriented Programming), 即 面向切面编程,其基本思想是在极少影响原程序的代码的前提下,在程序中的某些地方,使用某些方式,不可见的(即不在原程序中添加其他代码)为原程序切入一些额外的功能。

    优点

    • 减少代码间的耦合性,使功能具有拔插性,保证自己代码的清洁性。
    • 能够让你只关注自己的代码,不需要关注切面是如何实现的。

    术语

    通知(advice)

    其定义了切点什么时候去增强,是在方法调用前,还是调用之后,还是前后都是,还是抛出异常时。

    • Before 某方法调用之前发出通知。
    • After 某方法完成之后发出通知,不考虑方法运行的结果。
    • After-returning 将通知放置在被通知的方法成功执行之后。
    • After-throwing 将通知放置在被通知的方法抛出异常之后。
    • Around 通知包裹在被通知的方法的周围,在方法调用之前和之后发出通知
    连接点(join point)

    可以被作为切点的地方,都可以被认为是链接点。

    切点(point cut)

    按照规则被选中的链接点,可以被称作为切点。

    Aspect(切面)

    aspectpointcountadvice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.

    目标对象(Target)

    织入 advice 后的目标对象. 目标对象也被称为 advised object.

    引入(introductions):
    • 引入允许你添加一个新的方法给已经存在的类。

    Spring对AOP的支持

    • Spring建议在Java中书写AOP
    • Spring是在运行阶段才将切面编织进bean中,是使用代理类。
    • Spring只支持方法级别的连接点。

    AOP应用

    XML形式的AOP

    proxy-target-class="true"指定使用GCLIB代理,如果proxy-target-class="false"或者没设置,则默认使用动态代理,但是如果代理类没有实现接口,则依然会使用GCLIB代理。

    aop:pointcut指定了切点。

    aop:advisor指定了通知时机,同样的还有aop:before aop:after

    需要注意的是spiritCommonInterceptor实现了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);
            // 不做限流降级处理
            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);
                // 执行方法
                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来配置。但Spring默认不支持@Aspect风格的切面声明,通过如下配置开启@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("方法执行前执行.....");
        }
        
        @AfterReturning(value="init()")
        public void afterReturning(){
            System.out.println("方法执行完执行.....");
        }
        
        @AfterThrowing(value="init()")
        public void throwss(){
            System.out.println("方法异常时执行.....");
        }
        
        @After(value="init()")
        public void after(){
            System.out.println("方法最后执行.....");
        }
        
        @Around(value="init()")
        public Object around(ProceedingJoinPoint pjp){
            System.out.println("方法环绕start.....");
            Object o = null;
            try {
                o = pjp.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
            }
            System.out.println("方法环绕end.....");
            return o;
        }
    }
    

    一个Annotation与AOP结合的例子

    要实现一个aop的功能,关键在于三个地方。

    • 通知(Advice) 定义了何时切。比如:before、around等。
    • 切点(PointCut) 定义了何处切。比如:execution(* com.mogujie.houston.openapi.api.impl..*(..))
    • 连接点(JoinPoint) 连接点是在应用执行过程中能够插入切面的一个点。能够利用它拿到应用的方法和参数等。
      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验证出现异常", e);
                return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "系统异常 请@Houston答疑, error:" + e.getMessage());
            }
            try {
                return joinPoint.proceed();
            } catch (Exception e) {
                logger.error("Service服务出现异常", e);
                return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "Service出现异常 请@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)//运行时使用
    public @interface TokenValidator {
    
        Class<? extends TokenValidationHandler> handler() default TokenValidationHandler.class;//定义了一个接口类
    
    }
    

    TokenValidationHandler

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

    一个Handler的实现

    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使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。

    JDK动态代理

    步骤

    1. 通过实现InvocationHandler接口创建自己的调用处理器
    2. 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
    3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
    4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
    
    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的动态代理

    CGLIB直接生成代理目标类的子类,不能对目标类中的final方法进行代理

    1. 查找A上的所有非final 的public类型的方法定义;

    2. 将这些方法的定义转换成字节码;

    3. 将组成的字节码转换成相应的代理的class对象;

    4. 实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理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 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

    不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。

    Javassist(介绍)

    Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。

    JDK动态代理与CGLIB性能比较

    1. 被代理接口
    public interface Test {
    
        public int test(int i);
    }
    
    
    1. 实现类
    public class TestImpl implements Test {
        @Override
        public int test(int i) {
            return i + 1;
        }
    
        public void print() {
            System.out.println("111111");
        }
    
    }
    
    1. JDK代理类
    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代理类
    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. 测试类
    public class ProxyPerfTester {
    
        public static void main(String[] args) {
            //创建测试对象;
            Test nativeTest = new TestImpl();
            Test dynamicProxy = DynamicTest.newProxyInstance(nativeTest);
            Test cglibProxy = CglibTest.newProxyInstance(TestImpl.class);
    
            //预热一下;
            int preRunCount = 100000;
            runWithoutMonitor(nativeTest, preRunCount);
            runWithoutMonitor(cglibProxy, preRunCount);
            runWithoutMonitor(dynamicProxy, preRunCount);
    
            //执行测试;
            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. 结果
    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
    

    可见在JDK1.7下:

    • 运行速度,Native是最快的,JDK动态代理稍次之,CGLIB最慢。
    • 创建速度,Native是最快的,JDK动态代理稍次之,CGLIB最慢。

    相关文章

      网友评论

          本文标题:Spring AOP 使用与分析

          本文链接:https://www.haomeiwen.com/subject/cjlspxtx.html