代理模式(静态,动态)

静态代理模式

/*
proxy意思是代理,代理模式设计的初衷是为了让类在非必要时不必初始化,通过代理人完成它的工作就行
特别注意调用者并不知道自己调用的是代理人还是本体
这里采用了打印机打印一串字符为例子,打印机初始化需要很长时间,为了使只在必须要打印时候再初始化,采用了代理模式



复习要点
1.代理思想
2.代理人实例本体的加锁方案
3.利用反射在代理类不知道本体是谁的情况下调用他
*/

//抽象出实际执行者和代理人的共同点写成接口,用以规范和主类调用
interface Printable{
    void setPrinterName(String name);
    String getPrinterName();
    //这个print方法用于打印字符串,代理类也要有这个方法,原因是要客户端会使用这个方法,但是不能让他直接用本体
    void print(String str);
}


//主方法作为测试类
public class ProxyMain {
    public static void main(String[] args) {
        //客户端直接调用代理人
        Printable p1=new ProxyPrinter("Alice");
        System.out.println("打印机名字是"+p1.getPrinterName());
        p1.setPrinterName("bob");
        System.out.println("打印机名字是"+p1.getPrinterName());
        p1.print("hello world");
    }
}
public class ProxyPrinter implements Printable{
    private String name;
    Printer real;
    public ProxyPrinter(String name) {
        this.name = name;
    }

    @Override
    //这里的set方法是设置打印机的名字,当real已经有值,也就是这个代理人已经指向了一个打印机,就需要抛弃上一个名字改为新name了
    public void setPrinterName(String name) {
        if(real!=null) real.setPrinterName(name);
        this.name=name;
    }

    @Override
    public String getPrinterName() {
        return name;
    }

    @Override
    //这个打印类需要加锁,否则在多线程下可能生成多个打印机实例,但是对print方法直接加锁会导致难以并发,因此剥离出实例化操作进行加锁
    public void print(String str) {
        realize();
        real.print(str);
    }

    private synchronized void realize() {
        if(real==null){
            real=new Printer(name);
        }
    }
}
public class Printer implements Printable{
    private String name;

    //为了模拟大型系统初始化需要很长时间,这里在构造方法中让printer类休眠五秒
    public Printer(String name) {
        heavyJob();
        this.name = name;
    }

    private void heavyJob(){
        System.out.print("do heavy job  ");
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
                System.out.print(".");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void setPrinterName(String name) {
        this.name=name;
    }

    @Override
    public String getPrinterName() {
        return name;
    }

    @Override
    public void print(String str) {
        System.out.println(str);
    }
}

JoinPoint,PointCut,advice等AOP基础查看spring揭秘的143页。

静态代理的缺点

在静态代理中,即便要横织入的逻辑相同,但是由于目标对象的类型不同,也需要定义多个相应的代理对象,例如要给多个功能增加限制晚间访问的横织逻辑,我们依旧要为每个功能写出相应代理类,由此增添了许多重复的逻辑,如果有成百上千的功能,就要写成百上千的代理类,给维护和扩展带来了极大的不方便。

动态代理即是为了解决上述问题而诞生的,首先看一下Java自JDK1.3就提供的通过InvocationHandler接口和Proxy类实现的动态代理。

Java Dynamic Proxy

先写两个业务,要为这两个业务横织入晚间不可以执行的逻辑。

interface Play{
    void plan();
}
class PlayGame implements Play{
    public void plan(){
        System.out.println("msf playing game");
    }
}
interface Study{
    void plan();
}
class StudyEnglish implements Study{
    public void plan(){
        System.out.println("msf Learning english");
    }
}

接下来编写advice逻辑

class SleepInvocationHandler implements InvocationHandler{
    private Object target;

    public SleepInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("plan")){
            if(isNight()){
                System.out.println("it`s sleep time");
                return null;
            }
            return method.invoke(target,args);
        }
        return null;
    }
    //判断是不是晚上
    private boolean isNight() throws ParseException {
        SimpleDateFormat sf=new SimpleDateFormat("HH:mm");
        Date now,before,after;
        now=sf.parse(sf.format(new Date()));
        before=sf.parse("07:00");
        after=sf.parse("11:00");
        if(now.after(after)&&now.before(before)){
            return true;
        }
        return false;
    }
}

接下来编写测试类,两个业务各执行一次

public class test {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Play play = (Play) Proxy.newProxyInstance(Play.class.getClassLoader(),
                new Class[]{Play.class}, 
                new SleepInvocationHandler(new PlayGame()));
        play.plan();
        Study study = (Study) Proxy.newProxyInstance(Study.class.getClassLoader(), 
                new Class[]{Study.class}, 
                new SleepInvocationHandler(new StudyEnglish()));
        study.plan();
    }
}

注意Proxy类新建代理类方法的参数,返回接口的类加载器,要返回的类的反射,还有advice。

这种方式实现动态代理传入的必须是接口,因此当目标对象没有实现接口的时候就不能采取这种方式。

 对于Spring来说,它会采用动态字节码生成的方式动态生成代理对象。

CGLIB动态字节码

动态字节码生成扩展对象的原理是,我们可以对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆写来扩展父类的行为,借助CGLIB这样的动态字节码生成库,在系统运行期间动态的为目标对象生成扩展子类。

首先需要一个实现了Callback接口的拦截类,一般实现MethodInterceptor接口即可。

class PlanCtrl implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if(method.getName().equals("plan")){
            if (new SleepInvocationHandler(null).isNight()) {
                System.out.println("it`s sleep time");
                return null;
            }
            return methodProxy.invokeSuper(o,objects);
        }
        return null;
    }
}

随后利用Enhancer类创建代理对象

public class test {
    public static void main(String[] args) {
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(StudyEnglish.class);
        enhancer.setCallback(new PlanCtrl());

        StudyEnglish studyEnglish = (StudyEnglish) enhancer.create();
        studyEnglish.plan();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值