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();
}
输出结果: