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

本文介绍如何使用JDK动态代理实现方法执行时间的统计,包括自定义注解、类扫描及代理对象创建过程。

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;

@Component
public 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();
    }

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

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值