Java部分

JDK动态代理 案例

**需求: ** 简单的实现增强(AOP)

JDK动态代理只能代理实现了接口的类,不支持对类的直接代理

在此提供一个接口和一个实现类, 需要对接口实现类中的方法进行增强

1
2
3
4
5
// Interface
public interface IHello {
public void hello();
public void bye();
}
1
2
3
4
5
6
7
8
9
10
11
// Implement
public class HelloImpl implements IHello{
@Override
public void hello() {
System.out.println("Hello");
}
@Override
public void bye() {
System.out.println("Bye");
}
}

增强部分:

首先需要自定义类实现InvocationHandler接口, 其次重写invoke方法, 通过invoke方法进行代理: 案例如下:

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
public class ProxyHandler implements InvocationHandler {
private Object target; // 目标类

public ProxyHandler(Object target) {
this.target = target; // $Proxy01: hello() Bye()
}
// 生成代理对象方法
public Object createProxy(){
// JDK中提供了 Proxy类, 有一个方法专门用于根据接口生成代理类对象的方法
Object proxy = Proxy.newProxyInstance(ProxyHandler.class.getClassLoader(),
target.getClass().getInterfaces(),
this);
return proxy; // $Proxy01 Hello() Bye()
}

/**
* @param proxy 代理对象 $Proxy01
* @param method 调用的方法 hello()
* @param args 方法的参数值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 解释 @Pointcut("execution(* com.hang..add*(..))")
if(method.getName().equalsIgnoreCase("hello")){
// 增强
showTime();
}
// 利用反射机制调用目标类的目标方法
Object reflectObj = method.invoke(target, args); // HelloImpl.Hello() 目标类的方法

return reflectObj;
}
// 增强的方法
private void showTime() {
System.out.println("当前时间为: " + new Date());
}
}
  • 对于24-26行: 对于方法名为 hello进行增强, 此处以前置增强为例, 在方法执行(invoke)前, 执行自定义方法(showTime)显示时间

测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
// 在本地生成代理对象字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
IHello target = new HelloImpl(); // 目标类
ProxyHandler handler = new ProxyHandler(target);
// 生成代理类
Object proxy = handler.createProxy();
System.out.println(proxy); // Proxy0对象

IHello obj = (IHello) proxy;
obj.hello(); // $ Proxy0.Hello()
obj.bye();
}
}

输出如下:

1
2
3
4
com.hang.HelloImpl@61bbe9ba
当前时间为: Sat Nov 25 10:31:56 SGT 2023
Hello
Bye

可以很明显的看到, 在hello方法执行前, 输出了自定义增强方法showTime

此时需要观察生成代理字节码类可以在测试中添加

1
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

添加该行后, 在当前Module的同目录下会生成文件夹为com.sum.proxy ,文件夹内会生成字节码$Proxy0 … $Proxy20 … 数字部分可变

以当前案例生成的字节码为例: ($Proxy0.class)

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
public final class $Proxy0 extends Proxy implements IHello {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final void bye() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void hello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 省略equals,toString,hashCode三方法同上(为默认生成)

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.hang.IHello").getMethod("bye");
m3 = Class.forName("com.hang.IHello").getMethod("hello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
  • 通过该字节码文件可以很明显的发现一共生成了 5个 Method (两个为刚刚定义的方法, 三个(1.equals 2.toString 3.hashCode)为默认生成的)

  • 在代码尾部的static部分, 生成了方法上述的5个方法

以当前字节码文件中的 hello()为例 (因为我们对该方法增强了):

该方法主要逻辑为执行super.h.invoke(this, m3, (Object[])null);

  • 该方法很明确就是调用父类的 InvocationHandler

    1
    2
    // the invocation handler for this proxy instance.
    protected InvocationHandler h;

    然后调用invoke, 这个invoke不就是我们案例中重写了的invoke吗?

总结: 通过代理接口实现接口方法, 当调用时执行代理类中的同名方法然后回调到我们自定义的invoke, 在该方法内可以实现增强(前置,后置,环绕)

JDK谓词

直接上案例

1
2
3
4
List<String> ns= Arrays.asList("calyee blog","cal","ccaaalyyyyeeee");
ArrayList<String> names = new ArrayList<>(ns);
Predicate<String> predicate = s -> s.length() > 3; // 谓词
names.removeIf(predicate);

追踪Predicate

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
// 仅列出一个 (其他的还有比如或运算)
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
}

当前案例为: 筛选长度大于3的对象

其中removeIf方法为传入一个谓词对象, 通过谓词条件构造筛选条件, 然后重复遍历(类似于构造if-else判断)

所以: 谓词工厂就是一个判断

自定义实现Xxx

预定