您的位置:首页 > 编程语言 > Java开发

超简单,超详细,超快速,一下就懂Java(JDK中的)代理模式

2018-02-04 11:49 435 查看
代理模式是指,为其他对象提供一种代理以控制这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标类对象之间起到中介的作用.

1.1静态代理

先上代码有个直观感受

1.创建一个接口

//主业务方法:本接口中的方法将要被代理增强
public interface ISomeService {
String doFirst();
void doSecond();
}


2.再创建一个类实现这个接口(我们称他为目标类)

//目标类:代理类需要增强的类
public class ISomeServiceImpl implements ISomeService {
@Override
public String doFirst() {
System.out.println("执行doFirst");
return "abcde";
}
@Override
public void doSecond() {
System.out.println("执行doSeconed");
}
}


现在我想怎么做呢?我想让两个ISomeServiceImpl的实例化对象分别执行这两个方法,但是第二个对象在执行doFirst()的时候能够把打印出来的字母变成大写(功能增强).

于是我们的代理浓重登场

3.创建一个代理类

//静态代理类
public class ServiceProxy implement
d202
s ISomeService {     //实现相同的接口

private ISomeService target;
public ServiceProxy() {
target = new ISomeServiceImpl();                 //先来一个ISomeService的实例化对象
}
@Override
public String doFirst() {
// 调用目标对象的目标方法,该方法返回全小写字母
String result = target.doFirst();
// 增强:在这里将目标方法全小写字母变成全大写
result = result.toUpperCase();
return result;
}
@Override
public void doSecond() {
target.doSecond();                                //在这里并没有做更多的操作,代理的方法调用目标类的方法
}
}


4.建个test来测试测试吧

public class MyTest {
public static void main(String[] args) {
// 创建一个目标对象
ISomeService iservice = new ISomeServiceImpl();
System.out.println(iservice.doFirst());          // print:"执行doFirst"和"abcde"
iservice.doSecond();                             // 执行doSeconed,print:执行doSeconed

//同时我再创建一个代理类的话..
ISomeService pservice = new ServiceProxy();
System.out.println(pservice.doFirst());          // print:执行doFirst  ABCDE
pservice.doSecond();                             // 还是print:执行doSeconed
}
}


看了这个例子,是不是觉得静态代理不再神秘了?那么接下来,再把它小改进:在代理类中增加一个有参构造

//静态代理类
public class ServiceProxy implements ISomeService {
private ISomeService target;//还是先创建一个目标内的对象,代理人?哈哈
public ServiceProxy() {
target = new ISomeServiceImpl();
}
public ServiceProxy(ISomeService target) {//创建一个有参构造,传入目标类对象
super();
this.target = target;
}
@Override
//下面和之前一样
public String doFirst() {
// 调用目标对象的目标方法,该方法返回全小写字母
String result = target.doFirst();
// 增强:将目标方法全小写字母变成全大写
result = result.toUpperCase();
return result;
}
@Override
public void doSecond() {
target.doSecond();
}
}


对代理类做了这样的改变后,在测试类我们就可以这样做了:

public class MyTest {
public static void main(String[] args) {
ISomeService target = new ISomeServiceImpl();
ISomeService service = new ServiceProxy(target);
System.out.println(service.doFirst());// print:执行doFirst ABCDE
//假设我还想调代理前的方法..
System.out.println(target.doFirst()); //print:执行doFirst abcde
service.doSecond();
}
}


是不是感觉代理类和目标类的关系更加紧密0

了呢?

1.2动态代理

动态代理和静态代理的区别就是:静态代理有代理类,动态代理么有代理类.没有了律师团,怎么帮我打官司呢?当然只好请律师了

接口和实现类不变,但是我把ServiceProxy类删掉

直接上test类

public class MyTest {
public static void main(String[] args) {
final ISomeService target = new ISomeServiceImpl(); // 在内部类中不能引用一个非final的变量,这点需要注意

//重点来了:
ISomeService service = (ISomeService) Proxy.newProxyInstance(target
.getClass().getClassLoader(),      // 目标类的类加载器
target.getClass().getInterfaces(),// 目标类所实现的所有接口
new InvocationHandler() {// 口怕,第三个参数居然是匿名内部类..其实就是一个实现了InvocationHandler接口的类

// proxy:代理对象
// method:目标方法
// args:目标方法的参数列表
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
Object result = method.invoke(target, args);// 这里运用到了反射的知识
String stresult = result.toString().toUpperCase();
return stresult;
}
});

System.out.println(service.doFirst());// print:"执行doFirst"和"ABCDE"
System.out.println(target.doFirst());
}


下面我们把那个最长的方法处理出来看看:

newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler h)


其中:

interfaces - 代理类要实现的接口列表

h - 指派方法调用的调用处理程序

返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

在前面静态代理例子中:

ISomeService service = new ServiceProxy(target);


我们通过代理类的构造方法得到一个代理类实例,通过这个代理对象执行它自己类中代理的方法从而得到目的.

那我们回过头来看看动态代理:

newProxyInstance()方法同样可以返回一个指定接口的代理类实例,只不过是Object类型的,所以要转型为代理类接口.

ISomeService service = (ISomeService)Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)


接着我们再重点关注一下第三个参数:

再简单说一下:第三个参数实际上是一个InvocationHandler的接口的实例,单接口不允许直接创建实例,所以我们创建一个匿名类不理来实现这个接口

进一步发现,这个InvocationHandler接口实际只有一个方法:

invoke(Object proxy, Method method,Object[] args)


invoke方法在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。 (这是JDK的中文API文档说的…)

这就是方法增强的关键:

有invoke,有method,是不是想到了反射?

于是我们使用反射的invoke方法:

method.invoke(target, args);


这里传入代理对象以及参数,反射的method在newProxyInstance中传入的interfaces确定.具体就看最后调用哪个了.对反射有疑问的戳这里(Java反射的使用入门详解)

简言之,这一个方法执行了,就相当于目标类的方法执行了.

在本例子中,doFirst()是返回一个String类型,不过这里要求用Object来接收.

Object result = method.invoke(target, args);


接下来就进行增强:

String stresult = result.toString().toUpperCase();


最后将增强后的结果返回

return stresult;//终于把原来的doFirst()方法增强了


到这里就解释得差不多了.现在我调service.doFirst()得到的绝对是大写了哈哈哈!!!

但是还没完!我用service.doSecond()时却报了空指针异常..怎么回事呢?

原来我们在执行反射的invoke方法时,默认它是有返回值的(不如上面的result).

但doSeconed()却没有返回值,所以result=null.

然后null执行result.toString().toUpperCase()就报错了.

如何解决?

当然是在执行方法增强之前先判断一下result是否为空就好,不为空才执行(就是套一个if)

@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
Object result = method.invoke(target, args);// 这里运用到了反射的知识
if (result != null)
result = result.toString().toUpperCase();
return result;
}


就是这样,喵~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: