ZB-037-02注解如何工作的

注解是如何⼯作的?

  • 注解仅仅是⼀段信息,它⾃⼰⽆法⼯作
  • 换句话说,没有东⻄处理它们的话,注解没有任何卵⽤

打日志需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Main {
public static void main(String[] args) {
MyService service = new MyService();
service.queryDatabase(1);
service.provideHttpResponse("123");

// 手动在方法 内部 加 start - end

// 假设你有 100个方法呢?
// 假设打印的内容修改了呢?
// 假设打印的地方修改了呢?
}
}

class MyService {
public void queryDatabase(int param){
System.out.println("start");
System.out.println("query db" + param);
System.out.println("end");
}

public void provideHttpResponse(String param){
System.out.println("start");
System.out.println("provide http service" + param);
System.out.println("end");
}
}

我们创建一个注解 @Log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public @interface Log {
}


public class MyService {
@Log
public void queryDatabase(int param){
System.out.println("query db" + param);
}

@Log
public void provideHttpResponse(String param){
System.out.println("provide http service" + param);
}
}

public class Main {
public static void main(String[] args) {
MyService service = new MyService();
service.queryDatabase(1);
service.provideHttpResponse("123");
}
}
  • 运行后你发现,并没打印日志

动态字节码增强

  • 我们现在有一个 MyService 的类说明书
    • 它身上只有几个注解,但是没有触发任何的行为
  • 我们可以 动态生产一份新的说明书 如 MyServicePlus, 但是它并没有对应的字节码文件存在
  • MyServicePlus 完全是“运行时” 我们凭空根据 MyService 说明书 捏造出来的一份说明书
  • 这就是 动态字节码增强技术
    • 在此过程我们会 扫描这份说明书 提到@Log的地方,并把它的功能进行增强
    • 提供原先不存在的功能
    • 好处是:原先的代码不用改

我们用一个现有的轮子 —— bytebuddy

引入依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.1</version>
</dependency>

抄文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatcher;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

// 通过注解
MyService service2 = enhanceByAnnotation();
service2.queryDatabase(1);
service2.provideHttpResponse("abc");
service2.noLog();
}

private static MyService enhanceByAnnotation() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return new ByteBuddy()
.subclass(MyService.class)
.method(new FilterMethodAnnotatedWithLogMatcher())
.intercept(MethodDelegation.to(LoggerInterceptor.class))
.make()
.load(Main.class.getClassLoader())
.getLoaded()
.getConstructor()
.newInstance();
}

public static class LoggerInterceptor {
public static List<String> log(@SuperCall Callable<List<String>> zuper)
throws Exception {
System.out.println("before method called");
try {
return zuper.call();
} finally {
System.out.println("method end");
}
}
}

static class FilterMethodAnnotatedWithLogMatcher implements ElementMatcher<MethodDescription>{

@Override
public boolean matches(MethodDescription target) {
List<String> methodsWithLog = Stream.of(MyService.class.getMethods())
.filter(FilterMethodAnnotatedWithLogMatcher::isAnnotatedWithLog)
.map(Method::getName)
.collect(Collectors.toList());

return methodsWithLog.contains(target.getName());
}

private static boolean isAnnotatedWithLog(Method method){
return method.getAnnotation(Log.class)!=null;
}
}
}
  • @Log
1
2
3
4
5
6
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME) // 这个非常重要,代表注解的保留期
public @interface Log {
}
  • MyService.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyService {
@Log
public void queryDatabase(int param){
System.out.println("query db" + param);
}

@Log
public void provideHttpResponse(String param){
System.out.println("provide http service" + param);
}

public void noLog(){
System.out.println("I have no log!");
}
}

代码仓库