Spring-AOP

来源:互联网 发布:熊片数据库手机版 2.1 编辑:程序博客网 时间:2024/06/06 03:05

    • AOP简介
        • 理解
        • 术语
          • 通知advice
          • 连接点join point
          • 切点point cut
          • Aspect切面
          • 目标对象Target
          • 引入introductions
        • Spring对AOP的支持
    • AOP应用
        • XML形式的AOP
        • Annotation形式的AOP
        • 一个Annotation与AOP结合的例子
    • AOP原理
        • JDK动态代理
        • 基于CGLIB的动态代理
        • ASM介绍
        • Javassist介绍
        • JDK动态代理与CGLIB性能比较

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;@Aspectpublic 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@Componentpublic 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:1msCreate Dynamic Proxy17msCreate 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最慢。
原创粉丝点击