006 使用动态代理实现自定义注解功能
问题的提出:自定义一个注解,如@MyLog,当把此注解加在函数上时,该函数的调用会被自动日志。
解题思路:创建函数所在对象的动态代理,当该函数被调用时,在代理中进行日志。
两种方法:方法一使用JDK动态代理,只能对实现了接口的类进行代理;方法二使用CGLiB动态代理,可以对普通类进行代理。
1. 自定义MyLog注解
1 package org.example.annotation; 2 3 import java.lang.annotation.*; 4 5 @Target(ElementType.METHOD) 6 @Retention(RetentionPolicy.RUNTIME) 7 @Documented 8 public @interface MyLog { 9 String beginMsg() default "执行开始..."; 10 String endMsg() default "执行结束!!!"; 11 }
方法一:使用JDK动态代理。
先定义一个接口:
1 package org.example.business; 2 3 public interface IPerson { 4 boolean eat(String food); 5 6 String walk(String start, String finish); 7 8 void sleep(int seconds); 9 }
再实现上述接口,注意实现类的工厂方法createPerson():
1 package org.example.business; 2 3 import org.example.annotation.MyLog; 4 import org.example.proxy.ProxyInvocationHandler; 5 6 import java.util.concurrent.TimeUnit; 7 8 public class Person implements IPerson{ 9
// 由createPerson()创建的对象才能使@MyLog生效 10 public static IPerson createPerson(){ 11 Person person = new Person(); 12 //TODO: 此处可以对Person对象进行额外的初始化工作 13 return (IPerson) ProxyInvocationHandler.getProxy(person); 14 } 15 @Override 16 @MyLog(beginMsg = "starting eat", endMsg = "finishing eat") 17 public boolean eat(String food) { 18 System.out.println("the person is eating " + food); 19 return true; 20 } 21 22 @Override 23 public void sleep(int seconds) { 24 System.out.println("the person is sleeping..."); 25 try { 26 TimeUnit.SECONDS.sleep(seconds); 27 }catch (InterruptedException e){ 28 e.printStackTrace(); 29 } 30 } 31 32 @Override 33 @MyLog 34 public String walk(String start, String terminal) { 35 System.out.println("the person is walking from " + start + " to " + terminal); 36 return start + "--" + terminal; 37 } 38 }
最后实现InvocationHandler接口:
1 package org.example.proxy; 2 3 import org.example.annotation.MyLog; 4 5 import java.lang.reflect.InvocationHandler; 6 import java.lang.reflect.Method; 7 import java.lang.reflect.Proxy; 8 import java.time.LocalDateTime; 9 import java.time.format.DateTimeFormatter; 10 11 public class ProxyInvocationHandler implements InvocationHandler { 12 private Object target; 13 14 public ProxyInvocationHandler(Object target){ 15 this.target = target; 16 } 17 18 static boolean equalParamTypes(Class<?>[] params1, Class<?>[] params2) { 19 if (params1.length == params2.length) { 20 for (int i = 0; i < params1.length; i++) { 21 if (params1[i] != params2[i]) 22 return false; 23 } 24 return true; 25 } 26 return false; 27 } 28 29 @Override 30 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 31 Object result; 32 33 MyLog myLog = null; 34 Method[] methodList = target.getClass().getDeclaredMethods(); 35 for (Method m : methodList) { 36 if (m.getName().endsWith(method.getName()) && 37 m.getReturnType().equals(method.getReturnType()) && 38 equalParamTypes(m.getParameterTypes(), method.getParameterTypes())) { 39 40 myLog = m.getAnnotation(MyLog.class); 41 break; 42 } 43 } 44 45 if (myLog != null) { 46 System.out.println(myLog.beginMsg() + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 47 result = method.invoke(target, args); 48 System.out.println(myLog.endMsg() + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 49 } else { 50 result = method.invoke(target, args); 51 } 52 return result; 53 } 54 55 public static Object getProxy(Object target){ 56 return Proxy.newProxyInstance( 57 target.getClass().getClassLoader(), 58 target.getClass().getInterfaces(), 59 new ProxyInvocationHandler(target) 60 ); 61 } 62 63 }
在Main中测试:
1 package org.example; 2 3 import org.example.business.IPerson; 4 import org.example.business.Person; 5 6 public class Main { 7 public static void main(String[] args) { 8 9 IPerson person = Person.createPerson(); 10 person.eat("rice"); 11 person.sleep(1); 12 String s = person.walk("shanghai", "beijing"); 13 System.out.println(s); 14 } 15 }
输出结果如下:
starting eat2022-12-28T13:23:05.1305902
the person is eating rice
finishing eat2022-12-28T13:23:05.13759
the person is sleeping...
执行开始...2022-12-28T13:23:06.139794
the person is walking from shanghai to beijing
执行结束!!!2022-12-28T13:23:06.1487947
shanghai--beijing
方法二:使用CGLiB动态代理。
导入CGLiB依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
由于 JDK 8 中有关反射相关的功能自从 JDK 9 开始就已经被限制了,需要 Add VM options:
--add-opens java.base/java.lang=ALL-UNNAMED
来开启这种默认不被允许的行为。
定义一个普通类,该类可以不实现某个接口,注意该类的工厂方法createHuman():
1 package org.example.business; 2 3 import org.example.annotation.MyLog; 4 import org.example.proxy.MyLogMethodInterceptor; 5 6 import java.util.concurrent.TimeUnit; 7 8 public class Human { 9 10 // 由createHuman()创建的对象才能使@MyLog生效 11 public static Human createHuman(){ 12 Human human = (Human) MyLogMethodInterceptor.getProxy(Human.class); 13 //TODO: 此处可以对Human对象进行额外的初始化工作 14 return human; 15 } 16 17 @MyLog(beginMsg = "starting eat", endMsg = "finishing eat") 18 public boolean eat(String food) { 19 System.out.println("the person is eating " + food); 20 return true; 21 } 22 23 public void sleep(int seconds) { 24 System.out.println("the person is sleeping..."); 25 try { 26 TimeUnit.SECONDS.sleep(seconds); 27 }catch (InterruptedException e){ 28 e.printStackTrace(); 29 } 30 } 31 32 @MyLog 33 public String walk(String start, String terminal) { 34 System.out.println("the person is walking from " + start + " to " + terminal); 35 return start + "--" + terminal; 36 } 37 }
最后实现MethodInterceptor接口:
1 package org.example.proxy; 2 3 import net.sf.cglib.proxy.Enhancer; 4 import net.sf.cglib.proxy.MethodInterceptor; 5 import net.sf.cglib.proxy.MethodProxy; 6 import org.example.annotation.MyLog; 7 8 import java.lang.reflect.Method; 9 import java.time.LocalDateTime; 10 import java.time.format.DateTimeFormatter; 11 12 public class MyLogMethodInterceptor implements MethodInterceptor { 13 @Override 14 public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 15 Object result; 16 17 MyLog myLog = method.getAnnotation(MyLog.class); 18 if (myLog != null) { 19 System.out.println(myLog.beginMsg() + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 20 result = methodProxy.invokeSuper(target, args); 21 System.out.println(myLog.endMsg() + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 22 } else { 23 result = methodProxy.invokeSuper(target, args); 24 } 25 return result; 26 } 27 28 public static Object getProxy(Class<?> clazz){ 29 Enhancer enhancer = new Enhancer(); 30 enhancer.setClassLoader(clazz.getClassLoader()); 31 enhancer.setSuperclass(clazz); 32 enhancer.setCallback(new MyLogMethodInterceptor()); 33 return enhancer.create(); 34 } 35 36 }
在Main中测试:
1 package org.example; 2 3 import org.example.business.Human; 4 5 public class Main { 6 public static void main(String[] args) { 7 8 Human human = Human.createHuman(); 9 human.eat("bread"); 10 human.sleep(1); 11 String t = human.walk("杭州", "合肥"); 12 System.out.println(t); 13 } 14 }
输出结果如下:
starting eat2022-12-28T13:36:19.0685967
the person is eating bread
finishing eat2022-12-28T13:36:19.0925967
the person is sleeping...
执行开始...2022-12-28T13:36:20.1026827
the person is walking from 杭州 to 合肥
执行结束!!!2022-12-28T13:36:20.1116888
杭州--合肥