admin管理员组文章数量:1035537
反射3
编写程序的时候,我们肯定希望把注意力全部放到需要落地的业务功能上,可是很多时候事情并没有那么简单。比如写一个接口,需要接收一个特定的请求并进行处理,那除了这个接口的本身的功能外,还有很多杂七杂八的处理:请求要不要安全验证、要不要鉴权、请求处理完了以后要不要进行收尾工作等等。
以Person类所实现的接口Eater为例:按照当前的实现方法,Person对其的实现是“拿起来就吃”,现在我们要给吃这个动作加上一个前置和一个收尾工作(为了表达方便,在后文中这种操作统称为包装操作)。
代码语言:txt复制前置:判断一下这个玩意能不能吃。
收尾:洗碗。
实现一:直接写进eat()中。
修改当前Person的eater实现,将eat()的实现代码改为如下:
代码语言:java复制public void eat() {
System.out.println("check the food");
System.out.println("Person eat now");
System.out.println("wash the dishes");
}
Main的实现代码如下:
代码语言:java复制package com.sunhw.main;
import com.sunhw.user.Person;
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.eat();
}
}
代码语言:txt复制输出:
check the food
Person eat now
wash the dishes
这种方法的好处很明显:简单,没有任何额外处理和复杂机制,但是缺点很大。
第一个缺点,业务实现和包装操作全部写到一起,增加了代码的复杂度。本次例子中很特殊,包装操作非常简单:两个out输出而已,但是在更普遍的情况下包装操作的逻辑可比这个复杂的多。如果之后需求更新,比如eat()的业务实现要更新,或者eat()的包装策略有调整,或者干脆两个一起来。那到时候面对这一大坨代码,修改和调试起来是一件非常酸爽的事情。
第二个缺点,eat()方法是接口Eater中定义的,而实现该接口的恐怕不只有Person类,比如写一个Cat类,它也需要实现Eater接口。那Cat类实现Eater接口要不要添加上包装操作?添加的话,和Person类的包装是否一样(猫不会洗碗......吧)?如果有一天包装操作需要调整,那哪些类的Eater实现要改?哪些不要改?你要人工找出所有实现了Eater接口的类,人工判断这些类符合哪种调整策略,人工对这些类的每一个Eater接口实现函数进行手工调整,然后再测试。这个过程中你不能漏掉任何一个类,也不能漏掉任何一个类的任何一个接口实现,然后每个类的每个接口实现函数中的每个修改都不能出错,尽管它们很多都是重复的。结合一下第一个缺点,当代码量大了起来,这也是一个非常酸爽的事情。
实现二:静态代理
我们可以编写一个专门的类来专门实现这些包装操作,针对Eater接口,专门实现一个EaterProxy类用于代理实现Eater接口。
代码语言:java复制package com.sunhw.user;
public class EaterProxy implements Eater {
private Eater eater;
public EaterProxy(Eater eater) {
this.eater = eater;
}
@Override
public void eat() {
before();
eater.eat();
after();
}
private void before() {
System.out.println("check the food");
}
private void after() {
System.out.println("wash the dishes");
}
}
Main中的实现方法如下:
代码语言:java复制package com.sunhw.main;
import com.sunhw.user.EaterProxy;
import com.sunhw.user.Person;
public class Main {
public static void main(String[] args) {
Person p = new Person();
EaterProxy ep = new EaterProxy(p);
ep.eat();
}
}
这种方法的好处是在两个类的分工上更加明确:PersonProxy类不关心Person中的eat()到底是怎么实现的,它的关注重点只是在eat()的包装方法本身。这种方式可以把eat()的实现切割为两个切面:一个只关心eat()的业务落地,而不用关心任何非业务的部分;一个只关心和业务无关的包装操作,而不在乎业务的具体实现。
对其中的三个角色(只关心业务具体实现的Person、只关心包装操作的PersonProxy、只关心如何使用eat()的消费方Main)实现了一定程度的隔离。当其中一方,如包装操作调整时,另外两方,业务实现和消费者Main类,都是完全无感,在一定程度上实现了代码的解耦合。
实现三:动态代理
刚才的实现方法是静态代理,即提前把代理类写好,然后使用的时候,直接指定对应要用的代理类即可实现功能。优点之前已经说过了,缺点呢?对每一个接口,你都要独立实现至少一个(可能针对同一个接口的不同实现类,代理也不一样)专门的代理类才能进行使用。
考虑一下这样一种场景:计算执行时间。计算时间这个操作,操作很统一,也很简单:方法执行前记录下时间戳,方法执行后记录时间戳并减去执行前的时间戳。几乎每个接口的每个方法都能使用,而这正是麻烦的地方:你要为每个接口都专门写一个“计算时间代理类”吗?不仅是对现有的接口,还有未来可能会新出现的任何接口?那如果有一个类实现两个接口,两个接口的方法都要计时,怎么代理?说实话,也不是不能写,但是会特别麻烦,而且不好维护。
还有一种代理是动态的,即接口的代理实现类并没有预先写好,而是在程序运行的过程中生成的。还是以刚才说的计时为例,这次具体一点,代理FasterEater接口,代理内容为对其方法计时(学生党可真惨,吃饭还要计时)。我们编写一个实现计时逻辑的类TimerHandler,以下直接给出代码:
代码语言:java复制package com.sunhw.user;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerHandler implements InvocationHandler {
Object target;
public TimerHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target);
after();
return result;
}
private void before() {
System.out.println("start time is "+System.nanoTime());
}
private void after() {
System.out.println("finish time is "+System.nanoTime());
}
}
代码中target变量用于表示需要被代理的对象,before和after代码也非常简单,不再解释。有意思的是,虽然说是在代理FasterEater接口,但是在这个代码中,代理类TimeHandler并没有实现FasterEater接口,它实现了一个非常奇怪的接口:InvocationHandler。这个接口中只有一个方法:
代码语言:java复制public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
解释这三个参数,proxy表示代理对象(是代理对象,不是被代理对象),method表示要代理的方法,args表示调用method方法要传入的参数。这个函数的效果是,在之后的使用中,当需要通过代理调用method方法时,实际运行的是invoke方法。TimerHandler的好处是,它并不和某个真正要代理的接口绑定,任何一个接口,都可以通过TimerHandler实现代理计时。
处理类写好了,怎么让它代理FasterEater接口呢?这里还需要另一个类的配合。下面先给出Main类的代码,然后再来慢慢解释。
代码语言:java复制package com.sunhw.main;
import com.sunhw.user.FasterEater;
import com.sunhw.user.Student;
import com.sunhw.user.TimerHandler;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
Student student = new Student();
TimerHandler th = new TimerHandler(student);
// 生成代理类,并用接口引用它
FasterEater fe = (FasterEater)Proxy.newProxyInstance(
th.getClass().getClassLoader(),
new Class[]{FasterEater.class},
th);
// 调用代理类的接口方法
fe.eat();
// 输出代理类的类名看看
System.out.println(fe.getClass().getName());
}
}
代码语言:txt复制输出:
start time is 615004073845600
Person eat now
finish time is 615004080818900
jdk.proxy1.$Proxy0
这里用到了Proxy类的newProxyInstance方法,其函数声明如下:
代码语言:java复制public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
loader为类加载器对象,决定了生成的代理类应该由哪个类加载器加载(类加载器讲起来可太长了,这里不解释,反正就先用和代理处理方法类同一个就行了。);interfaces是一个接口数组,表示这个生成的代理类要代理哪些接口。对,它是数组,意味着你可以将你编写的代理逻辑同时用于多个接口(当然,它既然能代理接口,说明这个代理类肯定也实现了这些接口。这是你可以用其中的接口引用代理类的基础);最后h不用说了,就是我们刚才写的具体的代理实现。
当代码运行到Proxy.newProxyInstance方法时,JVM会直接生成对FasterEater的代理实现。相比于静态代理,动态代理由于并没有把代理关系写死在代码中,而显得更加灵活(TimerHandler可以代理任何接口,不一定非要是FasterEater接口);并且代理的逻辑只需要编写一次而非重复编写,减小了代码冗余,提高了代码质量。
反射3
编写程序的时候,我们肯定希望把注意力全部放到需要落地的业务功能上,可是很多时候事情并没有那么简单。比如写一个接口,需要接收一个特定的请求并进行处理,那除了这个接口的本身的功能外,还有很多杂七杂八的处理:请求要不要安全验证、要不要鉴权、请求处理完了以后要不要进行收尾工作等等。
以Person类所实现的接口Eater为例:按照当前的实现方法,Person对其的实现是“拿起来就吃”,现在我们要给吃这个动作加上一个前置和一个收尾工作(为了表达方便,在后文中这种操作统称为包装操作)。
代码语言:txt复制前置:判断一下这个玩意能不能吃。
收尾:洗碗。
实现一:直接写进eat()中。
修改当前Person的eater实现,将eat()的实现代码改为如下:
代码语言:java复制public void eat() {
System.out.println("check the food");
System.out.println("Person eat now");
System.out.println("wash the dishes");
}
Main的实现代码如下:
代码语言:java复制package com.sunhw.main;
import com.sunhw.user.Person;
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.eat();
}
}
代码语言:txt复制输出:
check the food
Person eat now
wash the dishes
这种方法的好处很明显:简单,没有任何额外处理和复杂机制,但是缺点很大。
第一个缺点,业务实现和包装操作全部写到一起,增加了代码的复杂度。本次例子中很特殊,包装操作非常简单:两个out输出而已,但是在更普遍的情况下包装操作的逻辑可比这个复杂的多。如果之后需求更新,比如eat()的业务实现要更新,或者eat()的包装策略有调整,或者干脆两个一起来。那到时候面对这一大坨代码,修改和调试起来是一件非常酸爽的事情。
第二个缺点,eat()方法是接口Eater中定义的,而实现该接口的恐怕不只有Person类,比如写一个Cat类,它也需要实现Eater接口。那Cat类实现Eater接口要不要添加上包装操作?添加的话,和Person类的包装是否一样(猫不会洗碗......吧)?如果有一天包装操作需要调整,那哪些类的Eater实现要改?哪些不要改?你要人工找出所有实现了Eater接口的类,人工判断这些类符合哪种调整策略,人工对这些类的每一个Eater接口实现函数进行手工调整,然后再测试。这个过程中你不能漏掉任何一个类,也不能漏掉任何一个类的任何一个接口实现,然后每个类的每个接口实现函数中的每个修改都不能出错,尽管它们很多都是重复的。结合一下第一个缺点,当代码量大了起来,这也是一个非常酸爽的事情。
实现二:静态代理
我们可以编写一个专门的类来专门实现这些包装操作,针对Eater接口,专门实现一个EaterProxy类用于代理实现Eater接口。
代码语言:java复制package com.sunhw.user;
public class EaterProxy implements Eater {
private Eater eater;
public EaterProxy(Eater eater) {
this.eater = eater;
}
@Override
public void eat() {
before();
eater.eat();
after();
}
private void before() {
System.out.println("check the food");
}
private void after() {
System.out.println("wash the dishes");
}
}
Main中的实现方法如下:
代码语言:java复制package com.sunhw.main;
import com.sunhw.user.EaterProxy;
import com.sunhw.user.Person;
public class Main {
public static void main(String[] args) {
Person p = new Person();
EaterProxy ep = new EaterProxy(p);
ep.eat();
}
}
这种方法的好处是在两个类的分工上更加明确:PersonProxy类不关心Person中的eat()到底是怎么实现的,它的关注重点只是在eat()的包装方法本身。这种方式可以把eat()的实现切割为两个切面:一个只关心eat()的业务落地,而不用关心任何非业务的部分;一个只关心和业务无关的包装操作,而不在乎业务的具体实现。
对其中的三个角色(只关心业务具体实现的Person、只关心包装操作的PersonProxy、只关心如何使用eat()的消费方Main)实现了一定程度的隔离。当其中一方,如包装操作调整时,另外两方,业务实现和消费者Main类,都是完全无感,在一定程度上实现了代码的解耦合。
实现三:动态代理
刚才的实现方法是静态代理,即提前把代理类写好,然后使用的时候,直接指定对应要用的代理类即可实现功能。优点之前已经说过了,缺点呢?对每一个接口,你都要独立实现至少一个(可能针对同一个接口的不同实现类,代理也不一样)专门的代理类才能进行使用。
考虑一下这样一种场景:计算执行时间。计算时间这个操作,操作很统一,也很简单:方法执行前记录下时间戳,方法执行后记录时间戳并减去执行前的时间戳。几乎每个接口的每个方法都能使用,而这正是麻烦的地方:你要为每个接口都专门写一个“计算时间代理类”吗?不仅是对现有的接口,还有未来可能会新出现的任何接口?那如果有一个类实现两个接口,两个接口的方法都要计时,怎么代理?说实话,也不是不能写,但是会特别麻烦,而且不好维护。
还有一种代理是动态的,即接口的代理实现类并没有预先写好,而是在程序运行的过程中生成的。还是以刚才说的计时为例,这次具体一点,代理FasterEater接口,代理内容为对其方法计时(学生党可真惨,吃饭还要计时)。我们编写一个实现计时逻辑的类TimerHandler,以下直接给出代码:
代码语言:java复制package com.sunhw.user;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerHandler implements InvocationHandler {
Object target;
public TimerHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target);
after();
return result;
}
private void before() {
System.out.println("start time is "+System.nanoTime());
}
private void after() {
System.out.println("finish time is "+System.nanoTime());
}
}
代码中target变量用于表示需要被代理的对象,before和after代码也非常简单,不再解释。有意思的是,虽然说是在代理FasterEater接口,但是在这个代码中,代理类TimeHandler并没有实现FasterEater接口,它实现了一个非常奇怪的接口:InvocationHandler。这个接口中只有一个方法:
代码语言:java复制public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
解释这三个参数,proxy表示代理对象(是代理对象,不是被代理对象),method表示要代理的方法,args表示调用method方法要传入的参数。这个函数的效果是,在之后的使用中,当需要通过代理调用method方法时,实际运行的是invoke方法。TimerHandler的好处是,它并不和某个真正要代理的接口绑定,任何一个接口,都可以通过TimerHandler实现代理计时。
处理类写好了,怎么让它代理FasterEater接口呢?这里还需要另一个类的配合。下面先给出Main类的代码,然后再来慢慢解释。
代码语言:java复制package com.sunhw.main;
import com.sunhw.user.FasterEater;
import com.sunhw.user.Student;
import com.sunhw.user.TimerHandler;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
Student student = new Student();
TimerHandler th = new TimerHandler(student);
// 生成代理类,并用接口引用它
FasterEater fe = (FasterEater)Proxy.newProxyInstance(
th.getClass().getClassLoader(),
new Class[]{FasterEater.class},
th);
// 调用代理类的接口方法
fe.eat();
// 输出代理类的类名看看
System.out.println(fe.getClass().getName());
}
}
代码语言:txt复制输出:
start time is 615004073845600
Person eat now
finish time is 615004080818900
jdk.proxy1.$Proxy0
这里用到了Proxy类的newProxyInstance方法,其函数声明如下:
代码语言:java复制public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
loader为类加载器对象,决定了生成的代理类应该由哪个类加载器加载(类加载器讲起来可太长了,这里不解释,反正就先用和代理处理方法类同一个就行了。);interfaces是一个接口数组,表示这个生成的代理类要代理哪些接口。对,它是数组,意味着你可以将你编写的代理逻辑同时用于多个接口(当然,它既然能代理接口,说明这个代理类肯定也实现了这些接口。这是你可以用其中的接口引用代理类的基础);最后h不用说了,就是我们刚才写的具体的代理实现。
当代码运行到Proxy.newProxyInstance方法时,JVM会直接生成对FasterEater的代理实现。相比于静态代理,动态代理由于并没有把代理关系写死在代码中,而显得更加灵活(TimerHandler可以代理任何接口,不一定非要是FasterEater接口);并且代理的逻辑只需要编写一次而非重复编写,减小了代码冗余,提高了代码质量。
本文标签: 反射3
版权声明:本文标题:反射3 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1748196248a2267678.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论