1

前言

刚上大学那会,英雄联盟火的一塌糊涂,当时每天都想着升到30级开启排位之旅。可是升到30级需要大把的时间不说,这时候匹配到的人,水平过于参差不齐,问候你全家的事经常发生,那个时候就想要是能有个代练帮我升到30级该多好啊.....下面我们就通过代码的方式找下代练-..-

什么是代理模式

为其他对象提供一种代理以控制对这个对象的访问

uml类图

clipboard.png

代理模式是一个使用率非常高的模式,像spring的aop,struts2的Form元素映射都采用了代理模式。下面我们来看下类图中三种角色的具体定义:

  • subject:抽象主题,可以是接口也可以是抽象类,用来进行业务定义。
  • realsubject:真实主题类,用来实现真正的业务逻辑。
  • proxy:代理类,也叫委托类。它负责控制对真实主题类访问的控制,将所有抽象主题定义的方法委托给realsubject类实现,并在这个过程加上预处理和善后工作,已达到增强功能的目的.

静态代理

静态代理模式其实就是在类设计阶段就将代理类考虑在内,而不是和动态代理和cglib代理一样动态生成代理类。下面我们通过玩英雄联盟代练的例子来说明下:

public interface IGame {

    void login();

    void playLOL();

}
public class IGamePlayer implements IGame {

    private String name;

    public IGamePlayer(String name) {
        this.name = name;
    }

    @Override
    public void login() {
        System.out.println(name+"登录游戏");
    }

    @Override
    public void playLOL() {
        System.out.println(name+"赢下了一局英雄联盟,获得了100金币");
    }
}
public class IGameProxy implements IGame {

    private IGamePlayer iGamePlayer;

    public IGameProxy(IGamePlayer iGamePlayer) {
        this.iGamePlayer = iGamePlayer;
    }

    @Override
    public void login() {
        iGamePlayer.login();
    }

    @Override
    public void playLOL() {
        iGamePlayer.playLOL();
    }
}
public class Client {

    public static void main(String[] args) {
        IGameProxy iGameProxy = new IGameProxy(new IGamePlayer("bin"));
        iGameProxy.login();
        iGameProxy.playLOL();
    }
}
测试结果:
bin登录游戏
bin赢下了一局英雄联盟,获得了100金币

可以这样理解,自己写代理类的方式就是静态代理。创建代理类和真实主题类,用代理类控制主题类的访问。游戏代练登录账号,打游戏升级,从而节省我的时间。

动态代理

动态代理就是在设计和实现阶段不需要关心代理谁,而是在运行时期才去指定代理哪个对象。spring的核心之一就是aop,俗称面向切面编程,其核心就是采用了动态代理机制。

  • jdk动态代理

保持IGAME接口和业务逻辑类不变。

//通知(这个例子用来统计登录接口的耗时)
 interface IAdvice {
     void execute();
}
//前置通知
public class BeforeAdvice implements IAdvice {

    @Override
    public void execute() {
        System.out.println("执行前时间:"+System.currentTimeMillis());
    }
}
//后置通知
public class AfterAdvice implements IAdvice {

    @Override
    public void execute() {
        System.out.println("执行后时间:"+System.currentTimeMillis());
    }
}
public class DynamicProxy{

    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }

    public Object getInstance(ClassLoader classLoader, Class[] interfaces){
        return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //连接点
                if (method.getName().startsWith("login")){
                    new BeforeAdvice().execute();
                }
                Object objec=method.invoke(target,args);
                if (method.getName().startsWith("login")){
                    new AfterAdvice().execute();
                }
                return objec;
            }
        });
    }
}
public class Client {

    public static void main(String[] args) {
        IGamePlayer gamePlayer =  new IGamePlayer("bin");
        DynamicProxy dynamicProxy = new DynamicProxy(gamePlayer);
        IGame proxy= (IGame) dynamicProxy.getInstance(gamePlayer.getClass().getClassLoader(),gamePlayer.getClass().getInterfaces());
        proxy.login();
        proxy.playLOL();
    }
}
测试结果:
执行前时间:1513498759281
bin登录游戏
执行后时间:1513498759282
bin赢下了一局英雄联盟,获得了100金币

在上面的例子中,我引用了一些aop的术语,例如连接点,通知。实现了一个非常简单的面向切面编程,由项目经验的可以看下springaop关于事务的配置,就会明白这样配置的含义了。
jdk动态代理是面向接口的,也就是说代理对象是根据目标对象的所有接口决定的。到底是怎么实现的呢,我们来看下这一段代码:

public Object getInstance(ClassLoader classLoader, Class[] interfaces){
        return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //连接点
                if (method.getName().startsWith("login")){
                    new BeforeAdvice().execute();
                }
                Object objec=method.invoke(target,args);
                if (method.getName().startsWith("login")){
                    new AfterAdvice().execute();
                }
                return objec;
            }
        });
    }

Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler()):这个方法是重新生成了一个对象,通过类加载器和该类所有的接口生成。当前生成的方法都是空的,具体逻辑都会由InvocationHandler来实现。InvocationHandler我采用了内部类的方式来实现,具体逻辑都是通过invoke来访问目标对象。其调用流程就是:client-->DynamicProxy-->InvocationHandler-->IGamePlayer .

  • cglib代理

jdk提供的动态代理是通过接口实现的,那么cglib就是通过生成目标类的子类实现的。假如你的目标类没有实现接口,又想使用动态代理,那么cglib是你的不二选择。

Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.但是有一点需要额外注意,若目标类是final或者目标方法是static和final的,则不会被cglib方法拦截

使用cglib需要引入cglib包或者spring-core(spring核心功能已经集成cglib)

public class IGamePlayer{

    private String name;

    public IGamePlayer(String name) {
        this.name = name;
    }

    public IGamePlayer() {
    }

    public void login() {
        System.out.println(name+"登录游戏");
    }

    public void playLOL() {
        System.out.println(name+"赢下了一局英雄联盟,获得了100金币");
    }
}
public class DynamicProxy implements MethodInterceptor {

    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance(){

        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(target.getClass());

        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //模拟事务
        System.out.println("事务开始了");
        method.invoke(target,objects);
        System.out.println("事务结束了");
        return null;
    }
}
public class Client {

    public static void main(String[] args) {
        IGamePlayer iGamePlayer = new IGamePlayer("bin");
        DynamicProxy dynamicProxy = new DynamicProxy(iGamePlayer);
        IGamePlayer proxy = (IGamePlayer) dynamicProxy.getProxyInstance();
        proxy.login();
        proxy.playLOL();
    }
}
测试结果:
事务开始了
bin登录游戏
事务结束了
事务开始了
bin赢下了一局英雄联盟,获得了100金币
事务结束了

如例子,我们定义了一个目标对象类,没有实现接口,通过cglib的Enhancer工具类指定父类和回调,创建代理类。实现MethodInterceptor,重写intercept,相当于jdk动态代理的invoke。

总结

代理模式分为静态代理和动态代理。最大的区别就是静态代理是自己写代理,而动态代理是通过运行时动态的生成代理类。动态代理是springaop的核心,又分为jdk代理和cglib代理,前者通过接口生成代理后者通过定义父类的子类来生成代理(类不能为final,方法不能为static和final)。
代理模式或许是大家接触的最多的模式,有了springaop和aspectj这样优秀的工具,我们拿来定义即可。在学习aop框架的时候,要先弄清一些专业名词,切面、切入点、通知、织入,理解这些名词,知道代理模式的原理,学起aop框架就会游刃有余了。


Alpaca
142 声望33 粉丝