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
杭州--合肥

posted @ 2022-12-28 13:37  面包车  阅读(276)  评论(0编辑  收藏  举报