• AOP 应用 性能


    AOP

    我的感觉是做些日志什么的比较好,比如在每个controller的api前后搞一下,或者做些metric。今天在spring里用了下AOP并简单的测了一下性能。

    使用

    业务类

    public class DAOImpl {
        public int access(int i) {
            System.out.println("dao invoked.");
            return i+1;
        }
    }
    

    业务类的bean定义

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            ">
        <context:annotation-config />
    
        <bean id="DAO" class="foo.bar.DAOImpl"/>
    
    </beans>
    

    下面通过两种方式来配置使用AOP的功能,目的就是在业务类方法前后增加一些附加的过程。通过AOP方式我们不需要修改目标方法的代码,只需要定义附加的方法即可。

    配置文件

    定义advisor

    public class BeforeAdvisor implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("before: " + method.getName());
        }
    }
    
    public class AfterAdvisor implements AfterReturningAdvice {
    
        @Override
        public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
            System.out.println("after: returning.");
        }
    }
    
        <bean id="afterAd" class="advisers.AfterAdvisor"/>
        <bean id="beforeAd" class="advisers.BeforeAdvisor"/>
        <aop:config>
            <aop:pointcut expression="execution(* foo.bar.DAOImpl.access(..))" id="dataAccess" />
            <aop:advisor id="afa" pointcut-ref="dataAccess" advice-ref="afterAd" />
            <aop:advisor id="bfa" pointcut-ref="dataAccess" advice-ref="beforeAd" />
        </aop:config>
    

    上述配置定义了切点,和相关的advisor,使用如下

    ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
    DAOImpl dao = context.getBean(DAOImpl.class);
    dao.access(1);
    

    此时运行的话已经会有增加了附加方法的输出。

    AspectJ方式

    其实spring内部是依赖AspectJ的。因为只使用spring配置文件,而不使用AspjectJ注解的方式,如果没有添加AspectJ依赖,依然编译无法通过。提供spring配置文件方式的形式可能是考虑到对外接口的统一问题,spring不想直接在使用方式上依赖于第三方的模式。不过AspectJ注解形式显然比spring配置方式方便太多,因而spring又开放了注解配置的方式。这需要将应用了AspectJ注解的类作为一个bean定义,这样spring在读取bean定义时如果发现是个AspectJ定义bean那么就会执行相关的AOP代理生成工作,和读取配置文件其实大同小异。

    定义AspectJ类

    @Aspect
    public class AspectTest {
        @Pointcut("execution (* *.access(..))")
        public void test() {
    
        }
    
        @Before("test()")
        public void beforeTest() {
            System.out.println("before access");
        }
    
        @After("test()")
        public void afterTest() {
            System.out.println("after access");
        }
    }
    

    修改一下spring配置文件加入以下内容:

        <aop:aspectj-autoproxy/>
    
        <bean id="aspectjBean" class="aspectj.AspectTest"/>
    

    然后再像先前那样运行即可使用。如果没有把前面的相关配置删除的话可以看到有两套advisor方法在作用,说明两种配置是可以共存的。

    性能

    在除去所有在DAOImpl和advisor中的输出语句后加入测试如下:

            long t1 = System.nanoTime();
            for (int i=0; i<1000000; i++) {
                if (i+1 != dao.access(i)) {
                    System.out.println("error.");
                }
            }
            long t2 = System.nanoTime();
            System.out.println(TimeUnit.NANOSECONDS.toMillis(t2-t1));
    

    在不使用AOP的情况下,调用一百万次时间在3~7ms左右。而在启用了AOP的情况下使用配置文件方式时在300ms左右而使用AspectJ时在700ms+,显然AOP实现造成了巨大的性能开销(不过想想还好,毕竟100w次,不过跟原来不用AOP比真是很挫)。

    proxy-target-class

    在spring配置文件的aop:configaop:aspectj-autoproxy中都可以指定一个布尔参数proxy-target-class,当其为true时一般生成被代理实例的子类,为false则采用动态代理的形式。

    当我们使用AOP时,比如上面的例子在main函数中最后再输出一个dao对象的类名:

            System.out.println(dao.getClass().getName());
            System.out.println(dao.getClass().getSuperclass().getName());
    

    会得到类似如下输出:

    foo.bar.DAOImpl$$EnhancerBySpringCGLIB$$66d38c16
    foo.bar.DAOImpl
    

    所以在使用了AOP后在进行一些基于class的操作时要比较小心。AOP实现可以使用CGLIB生成代码产生新类也可以使用JDK动态代理。当像上面的例子那样被代理的类没有实现任何接口时只能使用CGLIB方式通过继承实现。而继承有有些固有的限制是不可避免的,比如不能继承final类(编译时直接报错),advisor不能final方法(编译不报错,但是调用时不会进行AOP动作,只是进行原来的调用)。这就是proxy-target-classtrue时的效果。当其为false时且类有实现特定的接口比如

    // DAO.java
    public interface DAO {
        int access(int i);
    }
    
    // DAOImpl.java
    public class DAOImpl implements DAO {
        public final int access(int i) {
            //System.out.println("dao invoked.");
            return i+1;
        }
    }
    
    

    则可以使用JDK代理(是默认情况),当使用JDK代理时再输出本类与子类的名称如下:

    com.sun.proxy.$Proxy7
    java.lang.reflect.Proxy
    

    同时我们获取bean的方式也要对应的发生变化,不能通过具体类类型获取而需要以接口实现的接口类型获取:

            DAO dao = context.getBean(DAO.class);
    

    在这个例子中CGLIB只比JDK Proxy快了5%左右,并没有惊人的提高。

    expose-proxy

    从被代理的内部调用被AOP修饰的方法并不会应用AOP的相关advisor,比如将上述类接口与类定义修改为:

    public interface DAO {
        int access(int i);
        void access(int i, int j);
    }
    
    public class DAOImpl implements DAO {
        public int access(int i) {
            System.out.println("dao invoked.");
            return i+1;
        }
    
        public void access(int a, int b) {
    
        }
    }
    
    

    当在access(int)内调用access(int, int)方法时并不会执行AOP的advisor不管是JDK代理还是CGLIB子类。如果需要再内部调用时也应用advisor则需要将参数expose-proxy设置为true。同时在方法内部通过以下方式获取含有advisor的对象进行方法调用:

    ((DAO)AopContext.currentProxy()).access(0, i)
    
  • 相关阅读:
    CentOS7 时间设置与网络同步
    CentOS7 系统升级,删除centos7开机界面多余选,升级至最新的内核
    Docker 编排工具Rancher 2.0
    Docker 编排工具Rancher 1.6.18
    通过WSL在Windows下安装子Linux系统
    Docker 使用Dockerfile构建redis镜像
    初探PHP多进程
    nginx转发
    mime类型
    socket php
  • 原文地址:https://www.cnblogs.com/lailailai/p/4716338.html
Copyright © 2020-2023  润新知