1.代理模式
代理模式(Proxy),是为目标对象提供一种代理以控制对目标对象的访问,如下:
客户端在访问目标对象的时候,实际上是通过代理对象去访问,在代理对象中可以加入对应的逻辑。
代理模式分为两种:
静态代理: 在程序运行确定代理角色,并且明确代理类和目标类的关系
动态代理: 通过反射机制,在程序运行时动态创建和生成代理对象
2.静态代理
通过静态代理实现方法耗时统计, 如下:
功能接口:
public interface Animal
{
void funcA();
}
功能接口实现:
public class Dog implements Animal
{
@Override
public void funcA()
{
System.out.println("this is a dog");
}
}
功能代理类:
public class DogProxy implements Animal
{
private Dog dog;
public DogProxy(Dog dog)
{
this.dog = dog;
}
@Override
public void funcA()
{
long start = System.currentTimeMillis();
dog.funcA();
System.out.println("耗时: "+ (System.currentTimeMillis()-start)+"ms");
}
}
这就是通过静态代理实现函数耗时统计,但是弊端显而易见,每新增接口实现就要创建新的代理类,然后将目标对象传进去,实现耗时统计。
这种方式显然很难扩展,我们看一下动态代理的实现.
3.动态代理
动态代理的核心是使用java proxy类,为传进来的任意对象生成动态代理对象,这个动态代理对象默认实现了原始对象的所有接口,
3.1 java原生接口实现动态代理
功能接口:
public interface Animal
{
void funcA();
}
实现类:
public class Dog implements Animal
{
@Override
public void funcA() {
System.out.println("this is a dog");
}
}
代理实现,核心是Proxy.newProxyInstance方法和invoke
public class MyInvocationHandler
implements InvocationHandler
{
private final Object target;
private Object realObject;
public MyInvocationHandler(Object target)
{
this.target = target;
this.realObject = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
public Object getRealObject()
{
return realObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("耗时: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
}
在Main中调用
public class test {
public static void main(String[] args) {
Dog dog = new Dog();
Animal animal = (Animal) new MyInvocationHandler(dog).getRealObject();
animal.funcA();
}
}
在main方法中调用animal.funcA()方法时实际上是自动调用MyInvocationHandler::invoke方法,在Invoke方法中添加了统计函数耗时的逻辑。
Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this)方法创建了代理类实例,被代理的委托类就是Target,
并将Target中的方法调用分配给了MyInvocationHandler,实际就是执行Target中的任何方法都会调用MyInvocationHandler::invoke
接下来我们看一下guava中的实现,guava封装了Proxy.newProxyInstance,使用更加简洁。
3.1 使用guava实现动态代理
Animal类和Dog类同上
代理逻辑实现如下,继承AbstractInvocationHandler类:
public class MyInvocationHandler02
extends AbstractInvocationHandler
{
private final Object target;
public MyInvocationHandler02(Object realObject)
{
this.target = realObject;
}
@Override
protected Object handleInvocation(Object proxy, Method method, Object[] args)
throws Throwable
{
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("耗时: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
生成代理类并使用:
public static void main(String[] args) {
Dog dog = new Dog();
Animal animal = newProxy(Animal.class, new MyInvocationHandler02(dog));
animal.funcA();
}
借助guava只需继承AbstractInvocationHandler类,在handleInvocation方法中增加具体的逻辑实现,最后用newProxy生成对应代理类即可。
newProxy实现如下,就是封装了java的Proxy.newProxyInstance
4.动态代理在trino/presto中的实现
trino和presto代码中很多地方都实现了动态代理设计模式,以trino权限模块为例, 在AccessControlModule类中创建AccessControl实例时使用了动态代理,如下:
createAccessControl方法中返回AccessControl的具体实现,如果logger.isAuditEnabled()=true则返回代理对象loggingInvocationsAccessControl,newProxy方法是guava提供的,同上。
看一下LoggingInvocationHandler的构造方法:
这里delegate就是被代理的委托类AccessControlManager,
parameterNames为ReflectiveParameterNamesProvider对象
logger为logger::audit
核心方法:
在handleInvocation方法中增加了日志的逻辑,
LoggingInvocationHandler::invocationDescription方法如下,这里就是返回了日志的具体内容
this.parameterNames.getParameterNames方法如下,返回被代理委托类AccessControlManager的每个方法的参数
所以trino权限模块的日志输出逻辑如下:
->如果日志级别是audit则返回代理类loggingInvocationsAccessControl,被代理的委托类是AccessControlManager
->每次调用AccessControlManager中的方法时,先调用LoggingInvocationHandler::handleInvocation方法,通过method.invoke反射调用AccessControlManager的方法,
然后增加日志输出的逻辑及方法调用耗时
->对日志输出进行格式化
->如果日志级别小于audit,则权限模块不会有日志输出
最终日志输出如下:
2023-07-12T17:05:55.473+0800 | AUDIT | Query-20230712_090540_00019_rvykn-1052 | io.trino.security.AccessControl | Invocation of getColumnMask(context=SecurityContext{identity=Identity{user='trino', principal=trino}, queryId=20230712_090540_00019_rvykn}, tableName=hudi.hudi.hudi_cow_pt_tbl, columnName='hh', type=varchar) succeeded in 6.60us
2023-07-12T17:05:55.473+0800 | AUDIT | Query-20230712_090540_00019_rvykn-1052 | io.trino.security.AccessControl | Invocation of getColumnMask(context=SecurityContext{identity=Identity{user='trino', principal=trino}, queryId=20230712_090540_00019_rvykn}, tableName=hudi.hudi.hudi_cow_pt_tbl, columnName='$path', type=varchar) succeeded in 5.97us
2023-07-12T17:05:55.473+0800 | AUDIT | Query-20230712_090540_00019_rvykn-1052 | io.trino.security.AccessControl | Invocation of getColumnMask(context=SecurityContext{identity=Identity{user='trino', principal=trino}, queryId=20230712_090540_00019_rvykn}, tableName=hudi.hudi.hudi_cow_pt_tbl, columnName='$file_size', type=bigint) succeeded in 5.52us
2023-07-12T17:05:55.474+0800 | AUDIT | Query-20230712_090540_00019_rvykn-1052 | io.trino.security.AccessControl | Invocation of getColumnMask(context=SecurityContext{identity=Identity{user='trino', principal=trino}, queryId=20230712_090540_00019_rvykn}, tableName=hudi.hudi.hudi_cow_pt_tbl, columnName='$file_modified_time', type=timestamp(3) with time zone) succeeded in 5.62us
2023-07-12T17:05:55.474+0800 | AUDIT | Query-20230712_090540_00019_rvykn-1052 | io.trino.security.AccessControl | Invocation of getColumnMask(context=SecurityContext{identity=Identity{user='trino', principal=trino}, queryId=20230712_090540_00019_rvykn}, tableName=hudi.hudi.hudi_cow_pt_tbl, columnName='$partition', type=varchar) succeeded in 11.31us
2023-07-12T17:05:55.478+0800 | AUDIT | Query-20230712_090540_00019_rvykn-1052 | io.trino.security.AccessControl | Invocation of getRowFilters(context=SecurityContext{identity=Identity{user='trino', principal=trino}, queryId=20230712_090540_00019_rvykn}, tableName=hudi.hudi.hudi_cow_pt_tbl) succeeded in 3.99ms
2023-07-12T17:05:55.494+0800 | AUDIT | Query-20230712_090540_00019_rvykn-1052 | io.trino.security.AccessControl | Invocation of checkCanSelectFromColumns(context=SecurityContext{identity=Identity{user='trino', principal=trino}, queryId=20230712_090540_00019_rvykn}, tableName=hudi.hudi.hudi_cow_pt_tbl, columnNames=[_hoodie_commit_time, dt, hh, _hoodie_partition_path, _hoodie_record_key, name, _hoodie_commit_seqno, id, _hoodie_file_name, ts]) succeeded in 3.65ms
5.总结
trino中有非常多的动态代理实现,本篇文章只是分析了权限模块日志输出这一个场景,其他的实现也很值得研究,另外动态代理的底层原理后续会继续分析。