接口
接口本身是抽象方法的集合,不提供具体实现,与抽象类类似,但更像是针对方法的特化。
简单来说,接口更像是一份规格说明,任何实现该接口的类都需要完成规格说明上的任务,对这些方法进行重写。
多态
重写以及抽象类也是实现多态的方式,但在此不做赘述。
多态是根据接口的存在而延伸出来的一种编码操作。
实现了同一接口的两个不同类,就像是针对一份规格说明的两种实现方案,而多态则允许视情况选择不同的实现方案来完成开发过程中实际遇到的需求。
静态代理
代理模式本身属于多态的一种推广应用。
在静态代理的规则下,在编码过程中就需要确定具体的实现方案,也就是具体的接口实现类。
例如A同学需要买一支铅笔,而现在他有两种选择,一种是购买国产铅笔a,另一种则是购买进口铅笔b。此时我们定义一个接口“买铅笔”,这个接口就代表着A同学的规格说明,即A同学需要买一支铅笔。
然后我们定义两个不同的代理类,这两个代理类也就是不同的经销商,A同学可以通过这两个不同的经销商买到两种铅笔。
class DealerA implements BuyPencil
class DealerB implements BuyPencil
之后使A同学也实现该接口,此时A同学使一个委托类,委托类通过传入不同的代理类完成最终的逻辑实现,A同学也通过选择不同的经销商来完成铅笔的购买。
class ClassmateA implements BuyPencil{
private BuyPencil buyPencil;
public ClassmateA(BuyPencil buyPencil){
this.buyPencil = buyPencil;
}
@Override
public void buy(){
buyPencil.buy();
}
}
动态代理
相比较于静态代理在编码过程中就确定了需要载入的代理类,动态代理则在运行时根据反射机制动态生成代理类并注入到委托类中。
而在动态代理的过程中,需要用到两个部分:
- 接口InvocationHandler
- 类Proxy
首先需要准备一个动态代理类——它在代理过程中并不是代理类,也不是委托类,而是为了达成动态这一目的的中介类,也是管理类。
中介类必须实现InvocationHandler接口以完成工作:
public class DynamicProxy implements InvocationHandler{
//代理类
private final Object object;
//注入代理类
public DynamicProxy(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args){
//调用代理类的方法,并传入参数
Object result = method.invoke(object, args);
return result;
}
}
这一中介类并不关心真正的代理类,而只是提供了动态代理的功能。
实际上,在DynamicProxy类的invoke方法中,在return result前,你可以进行一些额外的操作,而这也就是AOP(面向切面编程)的原理。
本身invoke方法就像是一个拦截器一样,原本是向代理类提交参数,现在则是通过中介类,通过提交代理类以及参数的方式来调用代理类的方法。
在实际使用过程中,用法如下:
public class TestProxy{
public static void main(String args[]){
//代理类
Subject agent = new RealAgent();
//中介类
DynamicProxy proxy = new DynamicProxy(agent);
//委托类
Subject delegator = (Subject)Proxy.newProxyInstance(
agent.getClass().getClassLoader(),
agent.getClass().getInterfaces(),
proxy
);
delegator.buy();
}
}
Proxy.newProxyInstance方法也就是注入委托类的方法——传入代理类的类加载器以及接口列表,以及已经封装为InvocationHandler对象的真实代理类,由Proxy类生成一个经过包装后的代理类注入委托类中。
需要说明的是,以上的动态代理方式是JDK层面的动态代理,这种代理方式需要代理类和委托类实现一定的接口(否则Proxy类无法生成代理类),而另外存在CGLIB代理的动态代理方式,与JDK层面的动态代理相似,CGLIB同样存在一定的限制:代理类不能被final修饰,否则将无法使用在子类中进行方法拦截的技术完成动态代理的目的。
常见应用场景
- Spring声明式事务管理配置。
- Controller层的参数校验。
- 登录权限检查。
- 日志打印。