静态代理
静态代理在不修改目标对象的情况下,通过代理对象,对目标对象的功能进行增强。下面举个例子。已有ApiService和实现类ApiServiceImpl,现在想对业务功能扩展:统计保存的时间、记录查询的日志,但是不能改原来的方法。于是新增代理类ApiServiceProxy。
package demo.proxy;
public interface ApiService {
void save(String arg1, String arg2);
String query(String arg1);
}
package demo.proxy;
public class ApiServiceImpl implements ApiService {
@Override
public void save(String arg1, String arg2) {
String args = arg1 + arg2;
System.out.println("执行保存操作,参数:" + args);
}
@Override
public String query(String arg1) {
System.out.println("执行查询操作,参数:" + arg1);
return "success";
}
}
package demo.proxy;
public class ApiServiceProxy implements ApiService {
private ApiService apiService;
public ApiServiceProxy(ApiService apiService){
this.apiService = apiService;
}
@Override
public void save(String arg1, String arg2) {
long t1 = System.currentTimeMillis();
apiService.save(arg1, arg2);
long t2 = System.currentTimeMillis();
System.out.println("save方法执行毫秒数:" + (t2 - t1));
}
@Override
public String query(String arg1) {
String result = apiService.query(arg1);
System.out.println("记录安审日志");
return result;
}
}
package demo.proxy;
public class Client1 {
public static void main(String[] args) {
ApiServiceImpl apiServiceImpl = new ApiServiceImpl();
ApiServiceProxy proxy = new ApiServiceProxy(apiServiceImpl);
proxy.save("arg1", "arg2");
proxy.query("arg1");
}
}
通过静态代理,能够实现要求:扩展功能,不改原来的业务代码,实现业务增强。但是倘若不仅仅ApiServiceImpl需要记录日志和统计时间,其他的XXXServiceImpl也要扩展业务,程序里就有大量的代理类存在,如果Service发生改变,对应的Proxy也要变,因为Proxy实现了Service。究其根本,是因为静态代理是在编译期,就确定了代理类与被代理类的关系,如果能在运行期,动态的生成代理类,那就解决了大量代理类存在的问题,也解决了当Service改变Proxy代码也随之改变的问题。
JDK代理
JDK代理能够解决静态代理的痛点,避免大量的代理类。
在JDK代理中,代理对象不需要实现接口,但是目标对象一定要实现接口。其原理是通过反射机制,动态的生成目标对象的代理对象。下面来代码示例。
package demo.proxy2;
public interface FunctionService {
String query(String arg1);
}
package demo.proxy2;
public class FunctionServiceImpl implements FunctionService {
@Override
public String query(String arg1) {
System.out.println("执行query业务方法,参数:" + arg1);
return "success";
}
}
package demo.proxy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogProxyFactory {
private Object target;
public LogProxyFactory(Object target){
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = method.invoke(target, args);
System.out.println("记录日志,执行了" + method.getName() + "方法");
return result;
}
});
}
}
package demo.proxy2;
public class Client2 {
public static void main(String[] args) {
FunctionServiceImpl functionServiceImpl = new FunctionServiceImpl();
FunctionService func = (FunctionService) new LogProxyFactory(functionServiceImpl).getProxyInstance();
//这里只能用FunctionService来接收,而不能用实现类接收,因为创建的代理对象和实现类的对象,二者是兄弟关系,就好比ArrayList不能转LinkedList。
func.query("sa");
}
}
为什么JDK代理需要目标类实现接口呢?JDK代理的实现原理是什么呢?
首先,我们来复盘一下,JDK代理都经历了哪些步骤。
1.创建一个真实业务对象FunctionServiceImpl和LogProxyFactory对象。
2.把FunctionServiceImpl对象交给LogProxyFactory对象。
3.调用LogProxyFactory对象的getProxyInstance方法,拿到代理对象。在这个步骤中,通过Proxy类的newProxyInstance方法,我们拿到了代理对象。从表面上看,newProxyInstance方法的第一个参数,是ClassLoader,这个参数的实际意义是:生成代理对象使用的ClassLoader,一般我们就传入被代理对象的ClassLoader,我们创建代理对象,需要代理类加载,类加载需要类加载器。第二个参数,是Class<?>[],这个参数的实际意义是:被代理对象实现的接口,因为Java可以实现多个接口,因此这里是数组格式。讲到这里先停一下,为什么要传这2个参数?因为通过ClassLoader和代理类接口数组,就已经可以利用反射机制,动态的生成代理对象了。因为已知被代理类实现的接口信息,就能知道代理类需要实现的方法。换句话说,如果没有第三个参数,Proxy类的newProxyInstance方法创建出来的代理对象就是一个空壳。
class $ProxyN implements Interface1, Interface2{
public void foo1(){
//接口1定义的方法
}
public void foo2(){
//接口2定义的方法
}
}
接着说第三个参数InvocationHandler。这是一个接口,里面就一个方法invoke(Object proxy, Method method, Object[] args),假设已经动态创建了代理对象,在调用代理对象实现的接口方法时,这个代理对象究竟做什么活,就是由invoke来控制的。假设在LogProxyFactory类中的InvocationHandler匿名类里,我们不用method.invoke(target, args);,而仅仅是System.out.println一句话,那么调用任何代理对象实现的接口方法,都仅仅是System.out.println一句话。method.invoke(target, args)是指:当我们调用代理对象实现的接口里面的某个方法时,实际调用target对象的该方法。
4.把代理对象强制转型为FunctionService,并调用FunctionService接口中的方法,成功实现业务增强。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。