Java代理模式
一、什么是代理模式?
代理模式是对象的结构模式。 代理模式(Proxy)就是给某个对象提供一个代理对象,通过代理对象控制对原对象的引用。
这样可以在原对象的基础上增强格外的功能。
生活中处处都是代理。 举个例子:某明星可以去拍电影,唱歌,但不能什么事都自己干,所以他/她请了个经纪人替他排期,给明星安排什么时候拍戏,什么时候录歌。(可能不太恰当~~)
二、静态代理
- 身为明星,唱歌演戏不能少,所以有一个接口Star
public interface Star {
void makingMovie();
void singSong();
}
- 某X明星(被代理对象),自然也要会
public class XStar implements Star{
@Override
public void makingMovie() {
System.out.println("X拍电影");
}
@Override
public void singSong() {
System.out.println("X唱歌");
}
}
- 经纪人(代理对象)安排日常工作
public class Agent implements Star{
//通过组合的方式,持有一个XStar对象引用
private XStar xStar;
public Agent(XStar xStar){
this.xStar=xStar;
}
//代理对象安排时间演戏
private void arrangeMovie(){
System.out.println("经纪人安排了拍电影");
}
//代理对象安排时间唱歌
private void arrangeSong(){
System.out.println("经纪人安排了唱歌");
}
@Override
public void makingMovie() {
//经纪人安排后,X明星去拍戏
arrangeMovie();
xStar.makingMovie();
}
@Override
public void singSong() {
arrangeSong();
xStar.singSong();
}
}
测试代码:
public class Test {
public static void main(String[] args){
//创建代理对象并持有一个被代理对象
Agent agent=new Agent(new XStar());
//实际上就是经纪人安排后,x明星演戏
agent.makingMovie();
agent.singSong();
}
}
结果:
经纪人安排了拍电影
X拍电影
经纪人安排了唱歌
X唱歌
透明代理(普通代理)
public class Agent implements Star{
private XStar xStar;
//在这里做了修改
public Agent(){
this.xStar=new XStar();
}
private void arrangeMovie(){
System.out.println("经纪人安排了拍电影");
}
private void arrangeSong(){
System.out.println("经纪人安排了唱歌");
}
@Override
public void makingMovie() {
arrangeMovie();
xStar.makingMovie();
}
@Override
public void singSong() {
arrangeSong();
xStar.singSong();
}
}
这时XStar对象并不是外界传入的,所以xStar对外界是透明的。
总结: 静态模式在不改变目标对象(被代理对象)的前提下,实现了对其的扩展,但一旦目标接口增加了方法,代理对象和目标对象都要进行修改,增加了维护成本。例如:Star接口多了个void rap(),那么我的XStar和Agent都要修改。
三、动态代理
动态代理指:程序在整个运行过程中不存在目标类的代理类(或者说看不到,在JDK内部叫 $Proxy0)。目标对象的代理对象只是由代理生成工具(如代理工厂类) 在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
使用JDK的java.lang.reflect.Proxy类实现动态代理,通过其静态函数newProxyInstance,自动生成一个动态代理对象。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
loader:目标类的类加载器,通过目标对象反射获取
interfaces:目标类实现的接口数组,通过目标对象反射获取
h:业务增强逻辑
InvocationHandler是一个接口,用于加强目标类的主业务逻辑,这个接口有一个方法invoke,具体的加强逻辑代码就写在该方法中,程序调用主业务逻辑时,会自动调用invoke方法。
public Object invoke(Object proxy, Method method, Object[] args)
proxy:生成的代理对象
method:代表目标类的方法
args:代表目标类方法的参数
由于该方法有代理对象自动调用,所以不需要我们给出
第二个参数method,在反射中学过,它也有invoke方法,用来调用目标类的目标方法,这两个方法虽然同名,但没任何关系。
使用时一般写为:method.invoke(target,args),target为目标类对象,args是上一层传来的参数。
- x明星现在厉害了,不用别人安排唱歌了,只需要一个经纪人来安排拍电影就行了。
public class Test {
public static void main(String[] args){
Star xStar=new XStar();
System.out.println(xStar.getClass());
Star agent=(Star) Proxy.newProxyInstance(xStar.getClass().getClassLoader(), xStar.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过判断method的名称来进行下一步操作
if(method.getName().equals("makingMovie")){
System.out.println("安排拍电影");
method.invoke(xStar,args);
}else{
System.out.println("不需要安排唱歌");
method.invoke(xStar,args);
}
return null;
}
});
agent.makingMovie();
agent.singSong();
}
}
结果:
安排拍电影
X拍电影
不需要安排唱歌
X唱歌
- 还可以对代码进行封装:
class StarInvocation implements InvocationHandler{
private XStar xStar;
public StarInvocation(XStar xStar){
this.xStar=xStar;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("makingMovie")){
System.out.println("安排拍电影");
method.invoke(xStar,args);
}else{
System.out.println("不需要安排唱歌");
method.invoke(xStar,args);
}
return null;
}
}
class StarProxy{
private XStar xStar;
private StarInvocation starInvocation;
public StarProxy(XStar xStar,StarInvocation starInvocation){
this.starInvocation=starInvocation;
this.xStar=xStar;
}
public Object getProxy(){
return Proxy.newProxyInstance(xStar.getClass().getClassLoader(),xStar.getClass().getInterfaces(),starInvocation);
}
}
public class Test {
public static void main(String[] args){
XStar xStar=new XStar();
Star agent=(Star) new StarProxy(xStar,new StarInvocation(xStar)).getProxy();
agent.makingMovie();
agent.singSong();
}
}
注意:jdk动态代理的是接口,因此要强制类型转换为接口,而不是代理类,否则报错。上面也说了,动态代理是不需要代理类的。
总结:
- 代理对象拥有目标对象的相同方法(参数二) ;用户调用代理对象方法时,都时在调用invoke方法 ;使用动态代理时目标对象必需要有接口
- 使用静态代理时,如果目标接口有很多方法,我们都要一一实现。
- 使用动态代理时,是利用JDKAPI动态生成代理对象,并默认实现接口全部方法。
四、CgLib代理
实现动态代理必须要有接口的,动态代理是基于接口来代理的(实现接口的所有方法),如果没有接口的话可以考虑cglib代理。cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能
- c明星实例(目标对象)
public class CStar {
public void sing(){
System.out.println("sing");
}
public void dance(){
System.out.println("dance");
}
public void rap(){
System.out.println("rap");
}
}
- 经纪人(代理对象)
class CAgent implements MethodInterceptor{
private CStar cStar;
public CAgent(CStar cStar){
this.cStar=cStar;
}
//代理对象获取函数
public CStar getProxyInstance(){
//工具类
Enhancer en=new Enhancer();
//设置需要代理的对象,也是父类
en.setSuperclass(cStar.getClass());
//设置代理对象,回调设计模式,设置回调接口对象
en.setCallback(this);//回调自己
//创建子类代理对象
return (CStar) en.create();
}
//回调函数,进行逻辑增强
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("安排唱、跳、rap");
method.invoke(cStar,objects);
return null;
}
}
测试代码:
public class Test1 {
public static void main(String[] args){
CStar cStar=new CStar();
CAgent cAgent=new CAgent(cStar);
CStar proxy=cAgent.getProxyInstance();
proxy.dance();
}
}
结果:
安排唱、跳、rap
dance
cglib-nodep-3.1.jar 百度网盘:pan.baidu.com/s/1VGb0UP9_… 提取码:o7wt
回调模式:
Java的接口提供了一种很好的方式实现回调。
简单的说,在A类中调用B类的C方法,然后B类回调A类中的D方法。方法D被称为回调方法。例如:类CAgent调用类Enhancer的setCallback(this)方法,并将回调对象,也就是自身,传给了Enhance类,Enhance类会在后续过程中回调类CAgent的intercept()方法