设计模式之-静态代理与JDK动态代理
一、设计模式
设计模式可以提高代码的应用性与拓展性,从而使系统更加健壮、易修改、拓展性更强
应用场景:大多数框架使用了很多设计模式,例如:struts的mvc模式,线程的启动等
二、代理模式
概念:提供了对目标对象的间接访问方式,便于在目标实现的基础上增加额外的功能操作(例:前拦截,后拦截等)
特点:在不修改源码的情况下,使原本不具备某种行为能力的类对象具有某种行为能力
这使用到编程的一个思想:不要随意去修改别人已写好的方法,如需修改,通过代理方式来扩展
1.静态代理
1)概念:一个静态代理只能代理实现了同一个接口或继承了同一个父类的类,为其扩展功能;如果想要为多个接口或多个父类的子类代理,则需要建立多个静态代理
2)静态代理实现步骤:
a,定义一个接口或超类(含功能,例方法1)
b,定义具体目标类实现接口或者继承超类
c,具体目标类重写接口或者超类中的方法1(增加具体业务逻辑)
d,定义代理类,通过构造函数传入接口类型或者超类类型的目标对象
e,定义方法2来拓展方法1的功能(增加新的业务逻辑,并通过目标对象调用原方法1---即在方法1的基础上增加新的功能)
e,定义一个测试类,通过向上转型,创建接口类或者超类的目标对象
创建代理类对象,并将目标对象传入
通过代理类对象,调用扩增功能后的方法2
通过静态代理,代理接口实现子类的代码示例如下:
//创建一个接口,内含一个工作方法
interface Work{
void work();
}
//创建一个类实现接口,并重写工作方法
class Employee implements Work{
public String name;
public Employee(String name){
this.name=name;
}
//重写工作方法,增加业务逻辑
@Override
public void work(){
System.out.println("员工"+name+"正在整理文件");
}
}
//创建一个代理类,扩展工作方法功能
class Proxycls{
private Work target;
public Proxycls(Work target){
this.target=target;
}
//创建一个方法来拓展功能
public void work1(){
System.out.println("开始工作时间"+new Date());
target.work();
System.out.println("结束工作时间"+new Date()); }
}
//创建一个测试类,测试结果
public class Test{
public static void main(String[] args){
//通过向上转型的方式创建目标对象
Work targetEmployee = new Employee("小丽");
//创建代理对象,并传日目标对象
Proxycls proxycls= new Proxycls(targetEmployee);
//通过代理对象调用扩展功能后的方法
proxycls.work1();
}
}
运行结果:
开始工作时间Sun May 15 11:59:46 IRKT 2022
员工小丽正在整理文件
结束工作时间Sun May 15 11:59:46 IRKT 2022
缺点:
1)一旦接口增加方法,目标对象要维护(jdk1.8新特性,接口中增加普通默认方法,一定程度上优化了这个缺点)
2)一个静态代理只能代理实现了同一个接口或者继承了同一个超类的子类,且只能代理原接口或者超类中已定义好的方法,不能代理任意接口/任意超类/任意方法
解释:如需要代理的主体类有很多个方法需要代理,那么代理类需要编写很多的代理方法
且有时在执行Proxy代理的Before,After业务逻辑操作代码都是相同的,重复代码太多)
3)在编译的时候已经确定了被代理的对象类型
2.jdk动态代理
动态代理是运行时,通过反射机制实现动态代理,能够代理实现了接口的各种实现类
动态代理只能能对实现了接口的类生成代理,而不能针对普通类
JDK中生成代理对象的API,代理类所在的包:java.lang.reflect.Proxy
Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
1)创建动态代理类
Proxy.getProxyClass0(loader,interfaces)
2)创建动态代理对象
1>Proxy.new ProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
动态代理对象命名方式:都是以$开头,proxy局中,最后一个数字表示对象的标号
static Object. newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ---返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
1-1>创建动态代理的静态方法中的参数解释:
---ClassLoader loader:类加载器,负责将类的字节码文件装载到JVM虚拟机,并为其定义类对象
---Class<?>[] interfaces:被代理类实现的接口类型,使用泛型方式确认类型
---InvacationHandler h:事件处理,执行目标对象方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入(InvocationHandler是一个接口,里面有一个invoke(Object proxy,Mehtod method,Object[] args) 方法)
注意:InvocationHandler只有一个方法invoke(),它是被代理对象所有方法的唯一实现
就是说无论调用被代理对象的哪个方法,其实都是在调用InvocationHandler的invoke(Object proxy,Mehtod method,Object[] args);
1-1-1>InvocationHandler中invoke方法参数解释:
---Object proxy:代理对象,也就是Proxy.newProxyInstance()返回的对象,通常不用(调用的某些方法会出现死循环)
---:Method method:表示当前被调用方法的反射对象
---:Object[] args:表示当前被调用方法的参数,如果没有参数,则args是一个零长数组
注意:invoke方法的返回值为Object类型,它表示当前被调用的方法的返回值
当被代理方法没有返回值时,返回的必须是null;
1-1-2>代理对象调用被代理对象中任意方法时,都是在调用InvocationHandler中的invoke方法解释:
---当使用代理对象调用被代理对象中的方法1时,就调用了$Proxy0类中的重写的方法1
---在$Proxy类重写的方法1中调用父类InvocationHandler类型的属性h,然后用h调用invoke方法
---(详见示例如下示例代码图片)
1-1-3>解释InvocationHandler中invoke方法第一个参数Object proxy出现死循环的原因:
1)无论通过动态代理对象,调用被代理对象上的哪个方法,其实都是在调用InvocationHandler的invoke方法,原被代理对象中的方法,作为参数传到了invoke方法中
2)如调用被代理对象的原方法,需要通过反射method.invoke(object,args)调用---此处object应是被代理对象
3)如通过反射调用被代理对象的原方法时,传入的不是被代理对象,而是代理对象,就会出现死循环
同理:由于在Proxy的底层源码中,重写了equlas,hashCode,toString方法,让其返回的是InvocationHandler的invoke方法,如果在InvocationHandler的invoke方法中,通过动态代理对象调用其equals,hashCode,toString方法,也会出现死循环
![示例代码图片如下]
(https://img-blog.csdnimg.cn/a36c8486f77b4e79a149bd08a56a6059.png#pic_center)
(https://img-blog.csdnimg.cn/08fd675428af4f6298dc3c7a6ed81051.png#pic_center)
(https://img-blog.csdnimg.cn/f98df26e2f5f4512a1a60d1d4afed3ef.png#pic_center)
(https://img-blog.csdnimg.cn/f96d1a780b154d799ab938ef93915c73.jpeg#pic_center)
(https://img-blog.csdnimg.cn/ccdcaf96a905416ca7d35336b7659391.png#pic_center)
2)动态代理步骤:
a,定义一个接口,内含功能(例方法1)
b,定义一个类1实现接口,并重写方法1(增加业务逻辑)
c,定义一个类2用于获取动态代理对象
在该类内部定义一个静态方法,使用Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);创建动态代理对象,并返回
重写InvocationHandler中invoke(Object proxy,Method method,Object[] args)方法
在其内部增加拓展功能,并且通过method对象的调用invoke(Object target,Object[] args);----即调用原目标对象中的方法
d, 定义一个测试类,测试结果
创建目标对象
通过类2调用其静态方法获得动态代理对象,并通过参数将目标对象传入
使用动态代理对象调用原目标类中的方法
代码示例如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
//测试类,测试运行结果
public class DynamicProxy01 {
public static void main(String[] args) {
//创建目标对象
Employee employee=new Employee("candy");
//获取动态代理对象,并传入目标对象
Work workproxy=(Work)GetProxyInstance.getProxyInstance(employee);
//通过代理对象调用原目标类方法
workproxy.work();
}
}
//定义一个接口,内含工作方法
interface Work{
void work();
}
//定义一个目标类
class Employee implements Work{
public String name;
public Employee(String name){
this.name=name;
}
//重写工作方法,增加业务逻辑
@Override
public void work(){
System.out.println("员工"+name+"正在整理文件");
}
}
//定义一个类获取动态代理对象
class GetProxyInstance{
private Object target;
//定义一个静态方法,传入目标对象,并返回其对应的动态代理对象
public static Object getProxyInstance(Object target) {
//InvocationHandle是一个接口,使用匿名内部类方式创建其对象,并重写其invoke方法
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始工作时间:"+new Date());
method.invoke(target, args);
System.out.println("结束工作时间:"+new Date());
//注意:目标对象原方法没有返回值时,InvocationHandler中invoke方法必须返回null;
return null;
}
});
}
}
3.动态代理的特点
1)代理对象的生成,是利用JDK的API,动态的内存中构建代理对象
2)运行时,通过反射机制动态代理接口实现类,对其进行业务操作
缺点:JDK动态代理拥有局限性,就是必须面向接口编程,没有接口,就没有办法实现代理
DK也不能对private的方法进行动态代理
(静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类来创建)
与poppy一起学习
有疑问或者建议欢迎留言,poppy看到后会回复解答~