Java代理
1.RTTI机制
RTTI, 全称是Run-Time Type Identification,译为运行时类型识别。最经典的例子就是Java中的多态机制,父类引用指向子类的对象,JVM总是能够找到对应的子类去执行对应的方法。例如:
class Instruments {
public void play(){
System.out.println("Instruments is playing")
}
}
class surnai extends Instruments {
public void play() {
System.out.println("surnai is playing")
}
}
class piano extends Instruments {
public void play() {
System.out.println("piano is playing")
}
}
//测试
public class test {
public static void main(String[] args) {
Instruments piano = new piano();
Instruments surnai = new surnai();
piano.play(); //output: piano is playing
surnai.play(); //output: surnai is playing
}
}
很显然,运行时JVM其实是知道这个引用所对应的具体的对象的类型的,并且清楚这个类的所有信息,这块信息就保存在class对象中。也就是说一个类会有一个对应的class对象,并且只会有一个,由Jvm加载,不是一次性就全部加载到Jvm中,加载时机是当我们第一次使用时,例如: Instruments piano = new piano();更准确的说,当第一次引用该类的静态成员时,会加载对应类的class对象(就是读取对应类的.class文件,将字节码读取到jvm中),这恰恰证明了其实类的构造函数也是静态方法,尽管没有static修饰。
一旦这个类的class对象加载完成,那么这个类的所有对象就由这个类的class对象来生成。
我们可以通过三种获取到类的class对象,Class类提供了很多强大的API,具体参见JDK。
反射
上文提到三种方式获取类的class对象,分别是如下三种:
piano p = new piano();
Class c = piano.class;
Class c2 = p.getClass();
Class c3 = Class.forName("xx.xx.xx"); //需要handle 异常:ClassNotFoundException
第一第二种很好理解,第三种有点特殊之处,显而易见的是它需要处理一个异常:ClassNotFoundException,而第一第二种不需要,这是因为第一第二种我们已经知道了piano这个类的存在,而第三种我们需要传入一个变量(变量通常是一个class文件的路径),很显然我们可能传入一个错误的路径,jvm无法找到对应的class文件。自然也生成不了对应的class对象了。
所以,对于Class.forName这种方式,我们是无法在编译时获取到这个类的class文件的,也就是运行时检查.class文件,这个.class文件可能是在我们的磁盘中,也可能是通过网络传输获取到。一但获取到,我们拿到了对应的class对象,那么这个类的一切信息都暴露出来了,我们甚至可以通过invoke去调用对应的方法。
代理
如果说我们可以通过一个类的class对象,最终调用到这个类的某个实例的具体方法,那么是不是可以理解为我们可以代理这个对象,所谓代理,那么我们就可以做很多其他的事,只要最终不影响到方法的调用即可。
静态代理
先从静态代理开始:
public interface Task {
void setData(String data);
}
public class TaskImpl implements Task {
@Override
public void setData(String data) {
System.out.println(data+ " Data is saved");
}
}
public class TaskStaticProxy implements Task {
private Task realObj;
public TaskStaticProxy(Task task) {
this.realObj = task;
}
@Override
public void setData(String data) {
System.out.println("执行真实对象的setData方法之前,我做了一些其他事...");
realObj.setData(data);
System.out.println("执行真实对象的setData方法之后,我做了一些其他事...");
}
}
//test
public class Test {
public static void main(String[] args) {
TaskStaticProxy taskStaticProxy = new TaskStaticProxy(new TaskImpl());
taskStaticProxy.setData("data");
}
}
//output:
执行真实对象的setData方法之前,我做了一些其他事...
data Data is saved
执行真实对象的setData方法之后,我做了一些其他事...
这就是所谓的静态代理,很容易理解。那么真实情况是如果需要实现一个类的代理,那么就需要创建一个新的代理类,所谓的静态,十分不方便,有没有办法可以动态的生成这个代理类呢?
动态代理(JDK)
JDK提供了动态代理的机制:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
Object result;
System.out.println("执行真实对象的setData方法之前,我做了一些其他事...");
result = m.invoke(target, args);
System.out.println("执行真实对象的setData方法之后,我做了一些其他事...");
return result;
}
}
//test
public class Test {
public static void main(String[] args) {
Task task = (Task) Proxy.newProxyInstance(new TaskImpl().getClass().getClassLoader(), new Class<?>[] { Task.class },new MyInvocationHandler(new TaskImpl()) );
task.setData("data");
}
}
//output:
执行真实对象的setData方法之前,我做了一些其他事...
data Data is saved
执行真实对象的setData方法之后,我做了一些其他事...
使用了Proxy的一个静态方法,传入classloader,Class数组,以及自己实现的MyInvocationHandler对象:
- ClassLoader:类加载器,需要一个类加载器来生成对应的类,注意这里的类加载器需要传入AppClassLoader而不能是其他ClassLoader,你只需要知道我们自己编写的类获取到的classLoader就是AppClassLoader,而像Object,String这些类对应的classLoader就是另外一个classLoader了,这里推荐一篇文章:http://blog.csdn.net/briblue/article/details/54973413。
- Class<?>[]:接口数组,传入该类实现的所有的接口
- InvocationHandler:传入自己实现的InvocationHandler,这个类中来编写代理前后的逻辑。
Spring中结合注解使用代理(AOP)
AOP是Spring提供的一个特性,其实现就是依托于代理,所谓的AOP就简单的理解为面向切面编程,可以想象一下一个个方法垂直排列,我们往中间横切一刀,产生了一个切面,这样所有的方法都需要经过这个面,而我们就可以在这个面上处理一些自己的逻辑。
结合注解,Spring使用AOP十分方便,例如现在我们对项目中所有的Rest接口的调用做一个打印Log的操作。
首先定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiLog {
String desc() default "";
}
接着,编写对应的切面
@Aspect
@Component
public class ApiLogAopHandler {
@Before("within(com.zysaaa.aopdemo.controller.*) && @annotation(apiLog)")
public void apiLogHandler(final JoinPoint joinPoint, ApiLog apiLog) {
//args
Object[] args = joinPoint.getArgs();
//apiLog
apiLog.desc();
}
对应的测试代码:
@RestController
public class ApiController {
@GetMapping("/book")
@ApiLog(desc = "获取book By Book ID")
public Object book(Long id) {
return null;
}
}
启动项目后,我们调用对应的api,例如:http://localhost:8082/book?id=10,DEBUG模式下,会进入到我们的切面中,
此时,一切信息都能获取到。