一、使用背景
将目标类包裹起来,对目标类增加一个前置操作和一个后置操作,比如添加日志,在调用目标类前、调用目标后添加日志。
感觉静态代理与动态代理的核心思想,都是根据目标类,拿到目标实现的接口,和目标类的方法。然后创建一个新的类。然后将自己的前置行为与后置行为填充到这个新的类里。
静态代理,就是我们自己手动去写这个过程
动态代理,就是由java的java.lang.reflect包下提供的一个Proxy类来实现
二、静态代理
实现思路:
举例,类HelloSerivceImpl 实现接口Hello Service。 我想在类HelloSerivceImpl前后添加操作。
则我自定义一个类,同样继承于Hello Service。将目标类作为自定义类的属性,然后将目标类包裹起来,在调用前与调用后,增加步骤。
我们将来再调用的时候,调用的是我们自定义的这个类。
Hello Service
public interface HelloService {
String hi(String name);
}
HelloSerivceImpl目标类
public class HelloServiceImpl implements HelloService{
@Override
public String hi(String name) {
System.out.println("======");
return "hi"+name;
}
}
自定义类
public class HelloProxy implements HelloService{
private HelloService helloService;
public HelloProxy(HelloService helloService) {
this.helloService = helloService;
}
@Override
public String hi(String name) {
System.out.println("AAAAAA");
String hi = helloService.hi(name);
System.out.println("BBBBBBB");
return hi;
}
}
调用
public class App {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
HelloService newHelloService = new HelloProxy(helloService);
String hi1 = newHelloService.hi("zhangsan");
System.out.println(hi1);
}
}
三、动态代理
静态代理遇到的问题:
1、静态代理,只能针对某个具体的接口去写,如果遇到多个接口,需要写多个静态代理
2、上述只写了一个方法,如果扩展成多个方法,还需要加代码。
3、如果目标类实现多个接口呢
动态代理:
1、不需要关心实现了多少个接口
2、不需要关心多少个抽象方法
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
同样也是需要收集(传参)目标类实现的接口及方法,才能生成代理类。
举例:
接口HelloService
public interface HelloService {
String hi(String name);
}
实现类HelloServiceImpl
public class HelloServiceImpl implements HelloService{
@Override
public String hi(String name) {
return "hi"+name;
}
}
使用Proxy类实现动态代理
HelloService helloService = new HelloServiceImpl();
HelloService helloService1 =(HelloService)Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),// 类加载器,通过类加载器获取一个新的对象 代理对象
helloService.getClass().getInterfaces(), // 通过反射,拿到这个对象实现的所有接口
new InvocationHandler() { // 匿名内部类
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy 代理类,这里用不上
// method
// args 参数
// 原方法前,加自己的方法
Object o = method.invoke(helloService, args);// 调用目标类里面的方法,传原来的那个对象
// 原方法后,加自己的方法
return o;
}
}
);
helloService1.hi("张三");
使用匿名内部类实现
// 匿名内部类
HelloService helloService2 =(HelloService)Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),// 类加载器,通过类加载器获取一个新的对象 代理对象
helloService.getClass().getInterfaces(), // 通过反射,拿到这个对象实现的所有接口
(proxy, method, args1) -> {
// proxy 代理类,这里用不上
// method
// args 参数
// 原方法前,加自己的方法
Object o = method.invoke(helloService, args1);// 调用目标类里面的方法,传原来的那个对象
// 原方法后,加自己的方法
return o;
}
);
使用泛型,封装一个公共的方法
public static <T> T getInstance1(T instance){
ClassLoader loader = instance.getClass().getClassLoader();
Object o = Proxy.newProxyInstance(
instance.getClass().getClassLoader(),
instance.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置操作");
Object invoke = method.invoke(instance, args);
System.out.println("后置操作");
return invoke;
}
}
);
return (T) o;
}
四、扩展:ClassLoader 的作用
我们都知道java程序写好以后是以.java(文本文件)的文件存在磁盘上,然后,我们通过(bin/javac.exe)编译命令把.java文件编译成.class文件(字节码文件),并存在磁盘上。
但是程序要运行,首先一定要把.class文件加载到JVM内存中才能使用的,我们所讲的classLoader,就是负责把磁盘上的.class文件加载到JVM内存中。
你可以认为每一个Class对象拥有磁盘上的那个.class
字节码内容,每一个class对象都有一个getClassLoader()
方法,得到是谁把我从.class
文件加载到内存中变成Class对象的。