导言
@别叫小宋又来给大家分享最近学习成果来了。这期小宋为大家分享的是设计模式——代理模式!
代理模式的概念
代理模式是指在不修改原代码的基础上,对原对象的功能进行修改或者增强
代理模式的概念:分为目标类和代理类
目标类:原对象,我们需要通过代理对象控制它的访问,扩展其功能
代理类:代理模式产生的对象,是原对象的“替身”。已经在原有基础上修改逻辑
代理类分为:动态代理和静态代理
动态代理相比较静态代理,动态代理的额优势在于可以跟方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前就已经是class文件了。
静态代理,也就是我们会手写代理类的代码。工程中有代码类的源码,代码类会编译后执行。
编写代理类,实现目标类的接口或者直接继承目标类。完成逻辑的修改
静态代理有两种实现方式:
一、实现接口:目标类和代理类去实现同一个接口
二、继承:代理类继承目标类
分析:静态代理虽然能够实现我们所说的代理模式,完成解耦,但是静态代理类的代码维护非常复杂。一旦接口或者父类发生了变动,代理类的代码就得随之修改,代理类多的时候维护比较麻烦。
1.实现接口代码实现:
- 先创建目标类
package 设计模式.静态代理;
public class Customer implements OrderInterface{
@Override
public String order(String foodName){
return "已经下单点了"+foodName;
}
@Override
public void test(){};
@Override
public void test2(){};
}
- 创建代理类
package 设计模式.静态代理;
public class DeliverClerk2 implements OrderInterface{
//把我们原来的对象传入,并保存到成员位置。也就是目标类对象
private OrderInterface source;
public DeliverClerk2(OrderInterface source) {
this.source = source;
}
@Override
public String order(String foodname) {
String result = source.order(foodname);
System.out.println("已经接受到订单,正前往取餐途中。。。");
System.out.println("已经取餐,正在派送。。。");
return result+",已经搅拌均匀";
}
@Override
public void test() {
}
@Override
public void test2() {
}
}
- 创建共同的接口:
package 设计模式.静态代理;
public interface OrderInterface {
public String order(String foodname);
void test();
void test2();
}
- 测试类(代码实现类)
package 设计模式.静态代理;
public class StaticDemoTest {
public static void main(String[] args) {
// //创建一个顾客对象
// Customer customer = new DeliveryClerk();
// String result = customer.order("麻婆豆腐");
// System.out.println(result);
//创建一个顾客对象
Customer customer = new Customer();
//创建代理对象,也就是外卖小哥
OrderInterface deliverClerk2 = new DeliverClerk2(customer);
// 调用代理对象的方法,可以看到增强之后的效果
String result = deliverClerk2.order("红烧肉");
System.out.println(result);
}
}
2.代理类
- 创建目标类
package 设计模式.静态代理;
public class Customer implements OrderInterface{
@Override
public String order(String foodName){
return "已经下单点了"+foodName;
}
@Override
public void test(){};
@Override
public void test2(){};
}
- 创建代理类并继承目标类
package 设计模式.静态代理;
public class DeliveryClerk extends Customer{
@Override
public String order(String foodName) {
String result = super.order(foodName);
System.out.println("已经接受到订单,正前往取餐途中。。。");
System.out.println("已经取餐,正在派送。。。");
return result+",已经搅拌均匀";
}
@Override
public void test() {
super.test();
}
@Override
public void test2() {
super.test2();
}
}
动态代理
动态代理,是在内存生成代理对象的一种技术,也就是整个代理过程在内存中进行,我们不需要手写代理类的代码,也不会存在代理类编译的过程,而是直接在运行期,在JVN“凭空”造出一个代理类对象供我们使用。一般使用的动态代理有以下两种:
一、基于JDK(接口)的动态代理
JDK自带的动态代理技术,需要使用一个静态方法来创建代理对象
它要求被代理对象,也就是目标类,必须实现接口
生成的代理对象和原对象都实现相同的接口,是兄弟关系
Proxy.newProxyInstence(ClassLoader loader,Class<?>[] interfaces,InvocationHander h)
//主要参数列表
// ClassLoader loader:固定写法,指定目标类对象的加载类加载器即可。用于加载目标类及其接口的字节码文件。通常,使用目标类的字节码对象调用getclassLoder()方法即可得到
//Class<?>interfaces:固定写法,指定目标类的实现的所有接口的字节码对象的数组通常,使用目标类的字节码对象调用getinterfaces()方法即可得到
// InvocationHander h:这是参数是一个接口,主要关注它里面唯一一个方法,invoke方法,它会在代理类对象调用方法时执行,也就是说,我们在代理类对象中调用任何接口中的方法时,都会执行到invoke中。
Object invoke(Object proxy,Method method,Object[] args)里面的参数详情:
// proxy:就是代理类对象的一个引用,也就是Proxy.newProxyInstance的返回值,此引用几乎不回用到,忽略即可。
// method:对应的是触发invoke执行的方法的Method对象。
// args:代理对象调用方法时,传递的实际参数
兄弟关系:并列的关系,不能互相转换,包容性较差。在后续学习Spring框架,配置JDK的动态代理方式,一定要用接口类型接收代理类。
代码实现:
- 创建目标类
package 设计模式.动态代理.jdk;
public class Customer implements OrderInterface {
@Override
public String order(String foodName){
return "已经下单点了"+foodName;
}
@Override
public void test(){
System.out.println("我是test");
};
@Override
public void test2(){};
}
- 创建接口
package 设计模式.动态代理.jdk;
public class Customer implements OrderInterface {
@Override
public String order(String foodName){
return "已经下单点了"+foodName;
}
@Override
public void test(){
System.out.println("我是test");
};
@Override
public void test2(){};
}
- 创建测试类
package 设计模式.动态代理.jdk;
import 设计模式.动态代理.jdk.explain.DeliveryClerk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicTest {
public static void main(String[] args) {
//准备一个目标对象,也就是顾客对象
Customer customer = new Customer();
//把InvocationHandler的定义抽取出来
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("order".equals(method.getName())){
//System.out.println(method.getName()+"我执行了。。。。"+args[0]);
Object result = method.invoke(customer, args);
System.out.println("已经接受到订单,正前往取餐途中。。。");
System.out.println("已经取餐,正在派送。。。");
return result+",已经搅拌均匀";
}else {
return method.invoke(customer,args);//使用method反射调用
//在原对象(目标类对象)中执行该方法
//并不修改其逻辑,也就是说,原封不动的
//调用原来的逻辑
}
}
};
//使用jdk的API,动态的生成一个代理对象
/*OrderInterface deliveryClerk =(OrderInterface) Proxy.newProxyInstance(
customer.getClass().getClassLoader(),
customer.getClass().getInterfaces(),
//第一种,new一个匿名内部类
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("order".equals(method.getName())){
//System.out.println(method.getName()+"我执行了。。。。"+args[0]);
Object result = method.invoke(customer, args);
System.out.println("已经接受到订单,正前往取餐途中。。。");
System.out.println("已经取餐,正在派送。。。");
return result+",已经搅拌均匀";
}else {
return method.invoke(customer,args);
}
}
}
// //第二种拉姆达表达式:
// (proxy,method,args1)->{
// System.out.println("我执行了。。。");
// return null;
// }
);*/
OrderInterface deliveryClerk = new DeliveryClerk(handler);
//调用代理对象,执行对应方法
String result = deliveryClerk.order("麻婆豆腐");
System.out.println(result);
deliveryClerk.test();
}
}
二、基于CGLIB(父类继承)的动态代理
第三方CGLIB的动态代理技术,也是可以使用一个静态方法来创建代理对象。
它不要求目标类实现接口,但是要求目标类不能是最终类,也就是不能被final修饰
因为CGLIB是基于目标类生成该类的一个子类作为代理类。所以目标类必须可被继承
Enhancer.create(Class type,Callback callback)
//主要参数列表:
//Class type: 指定我们要代理的目标类的字节码对象,也就是指定目标类的类型
// Callback callback: callback意思是回调,意思就是我们提供一个方法,它会在合适的时候帮我们调用它。回来调用的意思。
// Callback是一个接口,由于该接口只是一个名称定义的作用,并不包含方法的声明。我们使用时通常使用它的一个子接口MethodInterceptor
// MethodIntercepor接口中也只有一个方法,叫做intercept
Object intercept(Object proxy, Method method,Object[] args, MethodProxy methodProxy)
//参数含义:
//proxy:就是代理类对象的一个引用,也就是Enhancer.create的返回值,此引用几乎不回用到,忽略即可。
// method:对应的是触发intercept执行的方法的Method对象。
// args:代理对象调用方法时,传递的实际参数
// methodProxy:方法的代理对象,一般不作处理,可以暂时忽略
代码实现:
- 创建目标类:
package 设计模式.动态代理.cglib;
public class Customer{
public String order(String foodName){
//上万行
return "已经下单点了"+foodName;
}
public void test(){
System.out.println("我是test");
};
public void test2(){};
}
- 创建实现类(增强条件实现)
package 设计模式.动态代理.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DynamicTest {
public static void main(String[] args) {
//创建一个目标类对象,也就是顾客对象
Customer customer = new Customer();
//使用CGLIB创建代理对象
Customer deliveryClerk =(Customer) Enhancer.create(customer.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//判断,如果是order方法,则增强
if ("order".equals(method.getName())){
Object result = method.invoke(customer, args);
// System.out.println("已获得订单,去取餐路上。。。");
// System.out.println("已取餐,正在送餐。。。");
return result+",额外添加美味佐料:*****";
}else{
return method.invoke(customer,args);//原封不动的调用原来的逻辑。反射调用
}
}
});
//调用代理类的方法
String result = deliveryClerk.order("鱼香肉丝");
System.out.println(result);
}
}
总结
1.代理模式在java开发中是广泛应用的,特别是在框架中底层原理经常涉及到代理模式(尤其是动态代理)
2.静态代理和动态代理,实际使用时还是动态代理使用的比较多。原因就是静态代理需要自动手写代码,维护、修改非常繁琐,会额外引入很多工作量。也不能很好的使用配置完成逻辑的指定,所以使用较少。
3.基于JDK和基于CGLIB,实际使用时两个都会用到
1,在Spring中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现接口,Spring就会自定选择jdk的动态代理。而如果目标类没有实现接口,则spring会使用CGLIB。
2.我们在开发时,由于我们基于JDK的动态代理要求比较高,更不容易实现,很多人习惯于同一配置为使用CGLIB进行代理,也就是CGLIB更通用。
3.如果使用dubbo+zookeeper,底层进行代理时,最好配置定死使用CGLIB的方式进行代理。因为dubbo会使用基于包名的扫描方式进行类处理,而JDK的代理类生成的com.sun.proxy…格式。我们实际需要让代理类和目标类保持一样的包名,所以只有CGLIB能保持原包名不变生成代理类。