jdk动态代理,统计某个方法的执行时间

来源:互联网 发布:php程序员简历范文 编辑:程序博客网 时间:2024/05/29 12:59

1、需求

统计某个方法的执行时间,写个demo模拟三层结构,dao层,service层,action层,比如要统计UserServiceImpl里面的getUser方法执行了多长时间,代码可能会写成这样:

        public void testTime(){        long startTime = System.currentTimeMillis();        UserService service = new UserSrviceImpl();        service.getUser();        System.out.println("耗时"+(System.currentTimeMillis()-startTime));    }

但是,我们要求,service层,只关心业务上的东西,类似统计执行时间,打日志之类的不想该层里面写。JDK的动态代理可以实现这样的需求、

2、实现思路

1.扫描指定包名下的所有class.
2.写个Component注解,可以过滤一些不符合jdk动态代理要求的class
3.写个Statistics注解,只有标记了这个注解的方法才会被统计。
4.把所有创建好的代理对象存放起来。(模拟Spring ApplicationContext),通过getBean方法获得.
5.调用代理对象的时候,如果对象上的方法被标记了Statistics,同时注解的值为true,则会进行统计。

3、扫描所有类

3.1、在网上找到的一段代码,出处链接找不到了。通过这个方法,可以获得指定包的所有class的全路径

  /**      * 从包package中获取所有的Class      * @param pack      * @return      */      public static List<Class<?>> getClasses(String packageName){          //第一个class类的集合          List<Class<?>> classes = new ArrayList<Class<?>>();          //是否循环迭代          boolean recursive = true;          //获取包的名字 并进行替换          String packageDirName = packageName.replace('.', '/');          //定义一个枚举的集合 并进行循环来处理这个目录下的things          Enumeration<URL> dirs;          try {              dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);              //循环迭代下去              while (dirs.hasMoreElements()){                  //获取下一个元素                  URL url = dirs.nextElement();                  //得到协议的名称                  String protocol = url.getProtocol();                  //如果是以文件的形式保存在服务器上                  if ("file".equals(protocol)) {                      //获取包的物理路径                      String filePath = URLDecoder.decode(url.getFile(), "UTF-8");                      //以文件的方式扫描整个包下的文件 并添加到集合中                      findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);                  } else if ("jar".equals(protocol)){                      //如果是jar包文件                       //定义一个JarFile                      JarFile jar;                      try {                          //获取jar                          jar = ((JarURLConnection) url.openConnection()).getJarFile();                          //从此jar包 得到一个枚举类                          Enumeration<JarEntry> entries = jar.entries();                          //同样的进行循环迭代                          while (entries.hasMoreElements()) {                              //获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件                              JarEntry entry = entries.nextElement();                              String name = entry.getName();                              //如果是以/开头的                              if (name.charAt(0) == '/') {                                  //获取后面的字符串                                  name = name.substring(1);                              }                              //如果前半部分和定义的包名相同                              if (name.startsWith(packageDirName)) {                                  int idx = name.lastIndexOf('/');                                  //如果以"/"结尾 是一个包                                  if (idx != -1) {                                      //获取包名 把"/"替换成"."                                      packageName = name.substring(0, idx).replace('/', '.');                                  }                                  //如果可以迭代下去 并且是一个包                                  if ((idx != -1) || recursive){                                      //如果是一个.class文件 而且不是目录                                      if (name.endsWith(".class") && !entry.isDirectory()) {                                          //去掉后面的".class" 获取真正的类名                                          String className = name.substring(packageName.length() + 1, name.length() - 6);                                          try {                                              //添加到classes                                              classes.add(Class.forName(packageName + '.' + className));                                          } catch (ClassNotFoundException e) {                                              e.printStackTrace();                                          }                                        }                                  }                              }                          }                      } catch (IOException e) {                          e.printStackTrace();                      }                   }              }          } catch (IOException e) {              e.printStackTrace();          }          return classes;      }  

3.2、编写Component注解

注解需要作用在类上面,所以Target需要ElementType.TYPE

package com.junjiex.action.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Component {}

3.3、编写Statistics注解

这个注解需要作用在方法上,所以是@Target(ElementType.METHOD)

package com.junjiex.action.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Statistics {    boolean count() default false;}

3.4、模拟Spring,编写ApplicationContext

这里模拟一下Spring的调用方法,当然,Spring没有这么简单.

package com.junjiex.action.aspect;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.List;import java.util.Map;import com.junjiex.action.annotation.Component;import com.junjiex.utils.ClassUtil;public class ApplicationContext {    private static ApplicationContext instance = null;    private Map<String,Object> objsMap = null;    public static ApplicationContext getInstance() throws InstantiationException, IllegalAccessException{        if(instance==null){            synchronized (ApplicationContext.class) {                if(instance == null){                    instance = new ApplicationContext();                }            }        }        return instance;    }    private ApplicationContext() throws InstantiationException, IllegalAccessException{        objsMap = new HashMap<String, Object>();        init();    }    public void init() throws InstantiationException, IllegalAccessException{        //查找com.junjiex这个包和子包里面的方法        List<Class<?>> classes = ClassUtil.getClasses("com.junjiex");          for (Class<?> clas :classes) {            //只有Component的类才放到bean缓存里            if(clas.isAnnotationPresent(Component.class)){                if(!clas.isAnnotation() && !clas.isInterface() && !clas.isEnum()){                    Object instance = clas.newInstance();                    Object obj = Proxy.newProxyInstance(instance.getClass().getClassLoader(), instance.getClass().getInterfaces(), new AspectHandler(instance));                    objsMap.put(instance.getClass().getSimpleName(),obj);                }            }        }        }    public Object getBean(String className){        if(!objsMap.containsKey(className)){            throw new RuntimeException("the bean not found!");        }        return objsMap.get(className);    }}

在init方法里面通过ClassUtil.getClasses(“com.junjiex”);查询这个包下面的所有class ,getClass的具体实现在前面有。
然后遍历拿到的所有class,通过if(clas.isAnnotationPresent(Component.class))来过滤,只有Component注解的类才调用代理类去处理。而代理类的处理过程在AspectHandler里面。

3.5、AspectHandler的实现

package com.junjiex.action.aspect;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import com.junjiex.utils.AnnotationUtil;public class AspectHandler implements InvocationHandler {    //被代理的目标对象    private Object target;    public AspectHandler(Object target) {        super();        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        Method loggerMethod = target.getClass().getMethod(method.getName(),                method.getParameterTypes());        if(AnnotationUtil.isAnnotation(loggerMethod)){            long startTime = System.currentTimeMillis();            Object ret = method.invoke(target, args);            System.out.println(String.format("方法%s执行的时间%d", method.getName(),System.currentTimeMillis()-startTime));            return ret;        }        return method.invoke(target, args);    }}

在invoke方法里,首先会判断,method是否有Statistics注解,并且count为true,如果成立,则才会进行时间统计,
AnnotationUtil.isAnnotation(loggerMethod)是用于判断是否符合这些要求的,符合,则会进行统计:

            long startTime = System.currentTimeMillis();            Object ret = method.invoke(target, args);            System.out.println(String.format("方法%s执行的时间%d", method.getName(),System.currentTimeMillis()-startTime));

3.6、AnnotationUtil的实现

package com.junjiex.utils;import java.lang.annotation.Annotation;import java.lang.reflect.Method;import com.junjiex.action.annotation.Statistics;public class AnnotationUtil {    public static boolean isAnnotation(Method method) {        if (method.isAnnotationPresent(Statistics.class)) {            Annotation anot = method.getAnnotation(Statistics.class);            Statistics log = (Statistics) anot;            if (log.count()) {                return true;            }        }        return false;    }}

4.测试使用

UserSrviceImpl 标记上了Component,getUser方法标记上了@Statistics(count=true),则getUser方法会执行统计。

package com.junjiex.dao.service.impl;import java.util.List;import com.junjiex.action.annotation.Component;import com.junjiex.action.annotation.Statistics;import com.junjiex.bean.User;import com.junjiex.dao.UserDao;import com.junjiex.dao.impl.UserDaoImpl;import com.junjiex.dao.service.UserService;@Componentpublic class UserSrviceImpl implements UserService{    private UserDao userDao = new UserDaoImpl();    @Statistics(count=true)    @Override    public User getUser() {        try {            //执行太快,延时一下            Thread.sleep(20);        } catch (InterruptedException e) {            e.printStackTrace();        }        return userDao.getUser();    }    @Override    public List<User> listUser() {        return userDao.listUser();    }    @Override    public void deleteUser(User user) {        userDao.deleteUser(user);    }    @Override    public void updateUser(User user) {        userDao.updateUser(user);    }}

单元测试,测试两个方法,一个有统计信息输出,一个没有。而有统计输出的getUser仅需要配置一个@Statistics(count=true)

    @org.junit.Test    public void testProx() throws InstantiationException, IllegalAccessException{            UserService service = (UserService) ApplicationContext.getInstance().getBean("UserSrviceImpl");            service.getUser();            service.listUser();    }

输出结果:
这里写图片描述