引入
代理模式的概念:
使用代理模式创建代理对象,让代理对象控制目标对象的访问,并且可以在不改变目标对象的情况下添加一些其他功能。
为了更好的实现 高内聚,低耦合,我们通常不愿意去修改已经写好的类或方法,而有时候需要加上一些通用的功能,比如 打印日志之类的。
简单案例
实现用户登录时,打印出用户登录的时间(日志)
静态代理
目标类UserServiceImpl
public class UserServiceImpl implements UserService{
public boolean login(String userName,String password) {
if(userName.equals("sky") && password.equals("123")) {
System.out.println("sky登录成功");
return true;
}else {
return false;
}
}
}
接口UserService
public interface UserService {
public boolean login(String userName, String password);
}
代理类UserProxy
public class UserProxy implements UserService{
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public boolean login(String userName, String password) {
// TODO Auto-generated method stub
System.out.println("sky"+new Date().toLocaleString()+"登录");
return this.userService.login(userName, password);
}
}
测试类Test
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
UserProxy userProxy = new UserProxy();
//注入目标类
userProxy.setUserService(new UserServiceImpl());
userProxy.login("sky", "123");
}
}
运行结果
sky2020-4-10 21:15:01登录
sky登录成功
可见我们并没有修改UserServiceImpl里的login方法,但仍然实现了日志的打印。
这里从测试类简单回溯一下:
首先我们new了一个代理对象userProxy,然后我们又new了一个目标对象并把它注入给了代理对象。
然后执行了代理对象的login方法。
再看代理类,他和目标类都实现了相同的接口,并且代理类在重写login方法的时候return了目标类的login方法
,这就好比同一个方法重写了两次,一次是目标方法的实现,一个则是代理方法的“锦上添花”。
这样既没有破坏原方法,也给其添加了一些新功能。
但静态代理也有很大的局限性,就是目标函数太多时,要给每一个目标类都配上代理类,显然是个大工程
此时动态代理就显得方便很多
动态代理
动态代理是根据目标对象动态的生成代理对象。
目标类(UserServiceImpl)和接口(UserService)都和上述一样
这里直接给出动态代理对象生成器类LoggerHandler
其实就是日志处理器,这样一来,所有想实现日志打印的都可以借助此类动态生成自己的代理对象,并实现功能。
public class LoggerHandler implements InvocationHandler{
//可以接收对象(目标)
private Object delegate;
//代理对象的创建
public Object bind(Object delegate) {//获得目标对象并给其创建对应的代理对象
this.delegate = delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
}
/**
* method 你要调用的方法
* args 方法对应的参数
* resul 该方法的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
Object result = null;
System.out.println("方法名是:"+method.getName());
result = method.invoke(delegate, args);
//在代理类里增加功能,,此处增加的 日志 功能
System.out.println("日志 : " + args[0] + " at " + new Date().toLocaleString() + "登录");
return result;
}
}
测试类Test
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
LoggerHandler loggerHandler = new LoggerHandler();
//传入目标对象,,要用共同的接口
UserService userService = (UserService) loggerHandler.bind(new UserServicelmpl());
userService.login("sky", "123");
}
}
运行结果
方法名是:login
sky登录成功!
日志 : sky at 2020-4-10 21:33:49登录
同样简单回溯一下:
首先测试类(Test)还是先把日志生成器(LoggerHandler)new出来一个对象。
然后还是类似地将目标对象注入(bind方法,可以说是绑定),但对应的代理对象是用接口(User Service)接收的。
再看LoggerHandler类
首先他实现了InvocationHandler接口
JDK1.3之后加入了实现动态代理的API
InvocationHandler接口
使用静态方法Proxy.newProxyInstance()建立一个代理对象
利用invoke()方法来操作代理方法
这里针对一下Proxy.newProxyInstance()方法
Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
先说第二个参数:目标对象.获得类.接口加载器
看到这就不难理解为什么要用 接口 接收对应的代理对象了,我猜这里面可能利用了反射机制(瞎猜的)
再看第二个参数:目标对象.获得类.类加载器,,
接着看invoke方法里的第二个参数 Method,,
Method不是反射里常见的方法吗?这难道?
于是点进了InvocationHandler接口,然后又懵逼的出来了。
然后在newProxyInstance方法里找了一个间接的证据
菜鸡(我):说实话,这真的没搞懂,希望有会的大佬能告诉我一下,感谢。
关于反射的一些简单理解可以参考一下这篇文章:结合实例理解反射
再看newProxyInstance的第三个参数,这里是把自身传过去了。
鉴于刚才点进了InvocationHandler接口,所以有了自己的一点理解。
接口里很空,很多注释,还有一个空方法!(invoke)
因为动态代理要服务于不同的目标对象,每个目标对象各有不同,也会出现不同的代理对象,
所以这里的this应该是帮助目标对象更好的找到自己的代理对象吧。(猜测)
最后总结一下动态代理和静态代理的优缺点:
动态代理解决了静态代理难以复用的缺点,但在性能上有所折扣。