23种设计模式总结

23种设计模式总结

1 单例模式

单例模式很容易理解,就是每次需要使用一个类对象的时候,我们去拿我们第一次new

的对象,而不是每次需要都去new一个,同时我们也需要限制该对象不能直接new出来。单例模式有很多写法,它里面又分为懒汉和饿汉

1.1 最简单的单例模式(饿汉,线程安全,推荐使用)

public class SingletonTest {

    private final static SingletonTest instance = new SingletonTest();

    /**
     * 将构造方法改为私有的,就无法new了
     */
    private SingletonTest() {
    }

    public static SingletonTest getInstance() {
        return instance;
    }

    /**
     * 模拟并发情况,100个线程同时获取实例
     *
     * @param args
     */
    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonTest instance = SingletonTest.getInstance();
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}

我们需要person对象的时候,直接使用SingletonTest.getInstance()J就行了

1.2 懒汉模式

上面这个饿汉模式就带来一个问题:有时候我们没有使用到这个对象啊,可是它却在项目启动后就创建了对应类的对象。于是就产生了这个懒汉模式,我们第一次需要用的时候才new一个对象出来。

public class SingletonTest {

    private static SingletonTest instance;

    /**
     * 将构造方法改为私有的,就无法new了
     */
    private SingletonTest() {
    }

    public static synchronized SingletonTest getInstance() {
        if (instance == null) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new SingletonTest();
        }
        return instance;
    }

    /**
     * 模拟并发情况,100个线程同时获取实例
     *
     * @param args
     */
    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonTest instance = SingletonTest.getInstance();
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}

为了解决上面写法效率慢的问题,就产生了下面这种方式

public class SingletonTest {

    private static SingletonTest instance;

    /**
     * 将构造方法改为私有的,就无法new了
     */
    private SingletonTest() {
    }

    public static SingletonTest getInstance() {
        //双重判断,主要是为了解决第一次获取实例时的并发访问
        //以后获取实例的时候,不会运行加锁的代码,就不会影响效率了
        if (instance == null) {
            synchronized (SingletonTest.class) {
                if (instance == null) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new SingletonTest();
                }
            }

        }
        return instance;
    }

    /**
     * 模拟并发情况,100个线程同时获取实例
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonTest instance = SingletonTest.getInstance();
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}

还有一种比上面更完美的写法(推荐使用)

public class SingletonTest {

    /**
     * 这种方式是利用了jvm加载类只加载一次来保证线程安全的
     * 而且静态内部类在外部类被加载的时候不会被加载
     */
    private static class SingletonHolder {
        private final static SingletonTest instance = new SingletonTest();
    }

    /**
     * 将构造方法改为私有的,就无法new了
     */
    private SingletonTest() {
    }

    public static SingletonTest getInstance() {
        return SingletonHolder.instance;
    }

    /**
     * 模拟并发情况,100个线程同时获取实例
     *
     * @param args
     */
    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonTest instance = SingletonTest.getInstance();
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}

最后一种就是effective java这本书中介绍的一种方法,这种方式也是线程安全的

public enum SingletonTest {
    instance;

    /**
     * 在枚举中写正常的业务方法
     */
    public void m(){
        //该类正常的业务逻辑
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonTest instance = SingletonTest.instance;
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}

2 策略模式

现在有一个需求:用户登录后需要将用户信息缓存保存在内存中,还有用户的权限信息

public class UserService1 {

    //内存
    Map<String, Object> cache = new HashMap<String, Object>();


    /**
     * 登录
     */
    public void login() {
        User user = new User();
        user.setAge(13);
        user.setName("test");
        //登录成功了,要将用户信息保存到缓存中
        cache.put(user.getName(), user);
    }

    /**
     * 授权
     * 保存权限信息
     */
    public void authentizate() {

        //授权成功,要将用户权限信息保存到缓存中
        cache.put("test-perm", "admin");
    }
}

模拟认证授权

public class Main {
    public static void main(String[] args) {
        UserService1 userService1 = new UserService1();
        //模拟前端的登录请求
        userService1.login();
        //授权
        userService1.authentizate();

        //查看内存中缓存信息
        System.out.println(userService1.cache);
    }
}

这样写是没问题的吧。

项目上线一段时间后,发现用户人数过大,缓存放在内存中服务器压力过大,现在需要将以前所有保存到内存中的缓存保存到redis中。所以,我们是不是需要在每个方法上都修改一遍,将原来的cache.put();方法换为redisTemplate.opsForValue().set(),但是你要注意,我们项目中肯定不止两个地方用缓存,而且缓存也不仅仅只有添加的方法(增删改查,这是最基本的)。这样看起来那任务量可就大了,还容易出错。也不符合我们面向对象设计的开闭原则(对扩展开放,对修改关闭)。

现在我们使用策略模式优化一下

抽象一个接口,里面定义缓存的增删改查

public interface CacheDao {

     void add(String key,Object value);
     void delete(String key);
     void get(String key);

}

内存

public class MemoryDao implements CacheDao {

    //内存
    Map<String, Object> cache = new HashMap<String, Object>();

    public void add(String key, Object value) {
        cache.put(key, value);
    }

    public void delete(String key) {
        cache.remove(key);
    }

    public Object get(String key) {
        return cache.get(key);
    }
}

redis

public class RedisDao implements CacheDao {


    private RedisTemplate<String,Object> redisTemplate;

    public void add(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);

    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
}

具体业务类中使用

public class UserService2 {

    /**
     * 我们需要用哪种缓存策略,我们就传入对应策略的对象
     * 
     */
    private CacheDao cacheDao;
    
    public CacheDao getCacheDao() {
        return cacheDao;
    }

    public void setCacheDao(CacheDao cacheDao) {
        this.cacheDao = cacheDao;
    }


    /**
     * 登录
     */
    public void login() {
        User user = new User();
        user.setAge(13);
        user.setName("test");
        //登录成功了,要将用户信息保存到缓存中
        cacheDao.add(user.getName(), user);
    }

    /**
     * 授权
     * 保存权限信息
     */
    public void authentizate() {

        //授权成功,要将用户权限信息保存到缓存中
        cacheDao.add("test-perm", "admin");
    }
}

模拟认证授权

public class Main {
 
    public static void main(String[] args) {
        UserService2 userService2 = new UserService2();
        //切换只需要修改这一处代码
        userService2.setCacheDao(new RedisDao());
        userService2.login();
        userService2.authentizate();

        //查看内存中缓存信息
        
    }
}

对于策略模式,下面这个例子也适用

现在有一个需求:做一个植物大战僵尸的游戏,初期需求就两个角色

僵尸 行动方式 攻击方式
普通僵尸 用手抓
帽子僵尸 用帽子打

后期,我们需要需要增加几款僵尸

僵尸 行动方式 攻击方式
铁桶僵尸 用铁桶防御
撑杆僵尸 用杆子跳过去

行动方式和攻击方式是不是也可以看成一种策略,没有必要每种僵尸都写一次,而死抽象成一个接口,由接口实现具体的攻击方式或行动方式。僵尸抽象类聚合这两个接口。

//抽象僵尸类
public abstract class Zombie {

    //聚合两个策略接口
    private Attackable attackable;
    private Moveable moveable;

    //僵尸开始向前攻击
    abstract public void display();
}


public interface Attackable {

    void attack();
}

public interface Moveable {

    void move();
}

写到这里是不是都明白了,我们只需要实现这两个接口,实现具体的攻击方式,具体的行动方式,然后就可以做到组件复用,修改方便。

3 工厂模式

工厂设计模式有4种,简单工厂,静态工厂,工厂方法,抽象工厂

3.1 简单工厂

工厂可以根据参数的不同返回不同的产品,这就是简单工厂模式

产品需要有一个共同的父类或接口

就比如,我现在有一个车间,它生产两种类型的鼠标AB,当我向车间控制台输入A的时候,车间就给我生产一个A类型鼠标,输入B的时候就给我生产一个B类型鼠标。这样,这个车间就相当于一个简单工厂。

public class SimpleFactory {

    /**
 	 * 车间控制台
 	 */
    Mouse getMouse(String type){
        if ("A".equalsIgnoreCase(type)){
            return new AMouse();
        }else if ("B".equalsIgnoreCase(type)){
            return new BMouse();
        }
        return null;
    }
}

/**
 * 共同接口
 */
public interface Mouse {

    void type();
}


public class AMouse implements Mouse {
    public void type() {
        System.out.println("A类型鼠标");
    }
}

public class BMouse implements Mouse {
    public void type() {
        System.out.println("B类型鼠标");
    }
}

通过工厂创建

Mouse mouse1 = new SimpleFactory().getMouse("A");
Mouse mouse2 = new SimpleFactory().getMouse("B");

3.2 静态工厂

简单工厂每次需要获取工厂对象才能创建所需的对象,静态工厂就不用了。

public class StaticFactory {

    public static Mouse getMouse(String type){
        if ("A".equalsIgnoreCase(type)){
            return new AMouse();
        }else if ("B".equalsIgnoreCase(type)){
            return new BMouse();
        }
        return null;
    }
}

通过工厂创建

Mouse mouse1 =  StaticFactory.getMouse("A");
Mouse mouse2 =  StaticFactory.getMouse("B");

3.3 工厂方法

定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类

举个例子:还是要生产两种型号的鼠标,但是我现在不在一个车间生产了,我决定一个车间只生产一款型号,你需要哪个型号的鼠标,我就去建造生产哪种鼠标的车间,然后去那个车间拿。

/**
 * 准备一个地方,用来生产鼠标,但是现在还没有生产的设备
 * 等到用户决定要哪种鼠标,再购买对应的设备
 * 
 */
public interface Creator {

    Mouse factoryMethod();
}

public interface Mouse {

    void type();
}

public class AMouse implements Mouse {
    public void type() {
        System.out.println("A类型鼠标");
    }
}

public class BMouse implements Mouse {
    public void type() {
        System.out.println("B类型鼠标");
    }
}

现在我需要A类型的鼠标,那么我就在那块地方建一个A鼠标生产车间

public class AMouseCreator implements Creator {

    public Mouse factoryMethod() {
        return new AMouse();
    }
}

//现在就可以通过这个车间拿到鼠标了
Mouse mouse = new AMouseCreator().factoryMethod();

生产一段时间,发现需要B类型的鼠标,那么我就在那块地方再建一个B鼠标生产车间

public class BMouseCreator implements Creator {

    public Mouse factoryMethod() {
        return new BMouse();
    }
}

//现在就可以通过这个车间拿到鼠标了
Mouse mouse = new BMouseCreator().factoryMethod();

3.4 抽象工厂

到了抽象工厂这里,就不是简简单单生产一种类型的产品了,而是产品族。

举个例子:现在工厂生产两款产品,4个型号分别是A鼠标,B鼠标,A键盘,B键盘,现在产品不单卖,而是组合成一个套装,目前有2种组合(AA 上班族,BB 游戏党),用户需要那样的组合就购买哪种。以前是这样的,生产上班族套装,那么我就得去鼠标车间调整生产A鼠标,去键盘车间调整生产A键盘。过一段时间,要生产游戏党套装,那是不是又得去所有车间,都调整一遍。这个小公司才两种产品,如果是大公司呢,它还要生产主机,生产显示屏,它们也有不同的型号,10种产品组合成不同套装,每次切换生产套装的时候所有车间都得跑一趟,累还容易出错。

AMouse aMouse = new AMouse();
aMouse.type();
AKeyBoard aKeyBoard = new AKeyBoard();
aKeyBoard.type();

现在不这么干了,我们生产线的控制台上定两个模板,需要生产哪个套装就直接切换到对应模板,一间切换,是不是很省事

/**
 * 生产套装的模板
 */
public interface AbstractFactory {

    //生产键盘
   KeyBoard createKeyBoard();
   
    //生产鼠标
   Mouse createMouse();

}


/**
 * 上班族套装
 */
public class ASuitFactory implements AbstractFactory {

    public KeyBoard createKeyBoard() {
        return new AKeyBoard();
    }

    public Mouse createMouse() {
        return new AMouse();
    }
}

/**
 * 游戏党套装
 */
public class BSuitFactory implements AbstractFactory {

    public KeyBoard createKeyBoard() {
        return new BKeyBoard();
    }

    public Mouse createMouse() {
        return new BMouse();
    }
}

这样使用就简单了

//直接切换模板
AbstractFactory SuitFactory = new ASuitFactory();
//AbstractFactory SuitFactory = new BSuitFactory();
Mouse mouse=SuitFactory.createMouse();
KeyBoard keyBoard=SuitFactory.createKeyBoard();
mouse.type();
keyBoard.type();

3.5 总结

对于工厂模式,我们没有必要死扣概念,能够生产对象的都可以称之为工厂。工厂模式有很多好处,比如我们可以将对象创建后的属性设置写到工厂中,这样创建的对象就都有默认值了。

4 观察者模式

模拟小孩哭了,然后爸爸起来抱抱,妈妈起来喂奶的场景。

不使用观察者模式,这种模式有个严重问题,主线程一直循环,浪费资源

public class Main {
    public static void main(String[] args) {
        final Kid kid = new Kid();
        Father father = new Father();
        Mon mon = new Mon();

        //10秒后孩子开始哭
        new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(10000);
                    //小孩开始哭了,自动通知爸爸妈妈
                    kid.cry();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();


        //主线程一直循环,监视孩子是否哭泣
        while (true){
            System.out.println("盯着孩子");
            if (kid.isCry()){
                father.action();
                mon.action();
                break;
            }
        }
    }
}


public class Father{
    public void action() {
        System.out.println("baba起来抱着");
    }
}

public class Mon{
    public void action() {
        System.out.println("mama起来喂奶");
    }
}

public class Kid {

    private boolean cry = false;

    public boolean isCry() {
        return cry;
    }

    public void setCry(boolean cry) {
        this.cry = cry;
    }

    public void cry() {
        cry = true;
        System.out.println("哭哭哭");
    }
}

观察者模式

public class Main {
    public static void main(String[] args) {
        Kid kid = new Kid();
        //给小孩设置观察者
        kid.observers.add(new Father());
        kid.observers.add(new Mon());

        //小孩开始哭了,自动通知爸爸妈妈
        kid.cry();
    }
}

public interface Observer {

    /**
     * 小孩醒了,通知观察者
     */
    void wakeup();
}

public class Father implements Observer{
    public void wakeup() {
        System.out.println("baba起来抱着");
    }
}

public class Mon implements Observer {
    public void wakeup() {
        System.out.println("mama起来喂奶");
    }
}

public class Kid {

    List<Observer> observers = new ArrayList<Observer>();

    private boolean cry = false;

    public void cry() {
        cry = true;
        System.out.println("哭哭哭");
        //通知所有的观察者
        for (Observer observer : observers) {
            observer.wakeup();
        }

    }
}

其实要点就是被观察者中需要设置观察者列表,当被观察者做了某个动作的时候,通知观察者列表中的所有观察者。

在正常设计中,一般不会这么简单,观察者会根据被观察者的行为的不同做出不同的反应。就比如小孩的哭,有嚎啕大哭,有随便哭两声,不同的哭爸爸妈妈应该有不同的反应。

关键代码

//事件驱动
public class WakeupEvent {

    //哭的时间
    long timestamp;
    //哭的程度
    String loc;

    public WakeupEvent() {
    }

    public WakeupEvent(long timestamp, String loc) {
        this.timestamp = timestamp;
        this.loc = loc;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }
}


public interface Observer {

    /**
     * 小孩醒了,通知观察者
     * 每次通知需要传入事件驱动
     */
    void wakeup(WakeupEvent event);
}

public class Kid {

    List<Observer> observers = new ArrayList<Observer>();

    private boolean cry = false;

    public void cry() {
        cry = true;
        System.out.println("哭哭哭");

        WakeupEvent event = new WakeupEvent(2132, "daku");
        //通知所有的观察者
        for (Observer observer : observers) {
            observer.wakeup(event);
        }

    }
}

注意:observer,listener,hook,callback这些全是观察者。

5 责任链模式

web中的过滤器链就是它的典型实现。每一个过滤器负责一个功能,只有所有的过滤器都通过之后,才能处理业务逻辑,一个过滤器没有通过,就沿原路返回。下面就模拟一下过滤器链。

public class Request {
}

public class Response {
}

public interface Filter {
    /**
     * 模拟过滤器
     * @param request
     * @param response
     * @param filterChain
     */
    void doFilter(Request request,Response response,FilterChain filterChain);
}

public class FilterChain {
    //需要通过的过滤器
    List<Filter> filters=new ArrayList<Filter>();

    //当前执行到第几个过滤器
    private int index=0;

    public FilterChain() {
    }

    public FilterChain addFilter(Filter filter){
        filters.add(filter);
        return this;
    }
    
    //这个方法的名字可以随意取,我这里叫这个名字是为了模拟web中的过滤器链
    //实际使用时,两个名字一般不相同
    void doFilter(Request request, Response response){
        if ((index!=filters.size()-1)&&filters.size()!=0){
            //不是最后一个过滤器
            Filter filter = filters.get(index);
            index++;
            filter.doFilter(request,response,this);
        }else{
            //所有过滤器执行完了之后,开始执行自己的业务逻辑,然后再沿原路返回

        }
    }
}

public class StringFilter implements Filter {
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        //对request做一系列处理

        //放行
        filterChain.doFilter(request,response);

        //对response做一系列处理


    }
}

public class CharacterFilter implements Filter {
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        //对request做一系列处理

        //放行
        filterChain.doFilter(request,response);

        //对response做一系列处理


    }
}

使用

//模拟controller中的request和response
Request request = new Request();
Response response = new Response();

//创建一个过滤器链,并添加过滤器
FilterChain filterChain = new FilterChain()
    .addFilter(new StringFilter())
    .addFilter(new CharacterFilter());

//执行过滤器链
filterChain.doFilter(request,response);

6 装饰者模式

装饰者模式你可以理解为给已经做好的东西加点佐料。

举个例子:去书店买书,以前是直接购买,先在,我需要先验证书是否是正版的,按照传统的编程方式就是直接继承,重写买书方法,在买书方法中加入验证正版的代码。这种方式明显不好,如果我们还需要添加一个功能(指定出版社),是不是又得继承一次,那如果我们只需要指定出版社,不要验证正版这个功能呢,很明显就产生了类爆炸。

我们现在使用装饰者模式,装饰者模式抽象出一个买书的接口,业务类需要实现该接口,装饰类也需要实现该接口,并且装饰类中需要一个业务类的属性。

/**
 * 抽象接口,定义了买书方法
 */
public interface Purchasing {
    void purchase();
}

/**
 * 用户付钱买书
 */
public class User implements Purchasing {
    public void purchase() {
        System.out.println("购买一本书");
    }
}

/**
 * 抽象装饰类
 */
public abstract class Decorator implements Purchasing{

    /**
     * 可以是被装饰类,也可以是装饰类
     */
    private Purchasing purchasing;

    /**
     * 抽象的买书方法
     */
    public abstract void purchase();

    public Purchasing getPurchasing() {
        return purchasing;
    }

    public void setPurchasing(Purchasing purchasing) {
        this.purchasing = purchasing;
    }

}

/**
 * 正版
 */
public class GenuineDecorator extends Decorator{

    @Override
    public void purchase() {
        //验证是否正版
        System.out.println("是正版");

        getPurchasing().purchase();
    }
}


/**
 * 出版社
 */
public class PublishDecorator extends Decorator{

    @Override
    public void purchase() {

        //验证是否是某个出版社
        System.out.println("是人社");

        getPurchasing().purchase();
    }
}

测试

//被装饰类对象
User user = new User();
//装饰器
Decorator decorator = new PublishDecorator();
//被装饰类对象设置到装饰器中
decorator.setPurchasing(user);
//装饰成功
decorator.purchase();

7 适配器模式

举个例子: 手机充电电压是5v,而家庭电压是220v,是不能直接充电的,这时候就需要借助充电器将220v转化为5v,这就是典型的适配器模式。

/**
 * 该类表示家庭用电电压
 */
public class Voltage220v {

    public int output220v(){
        System.out.println("输出220v电压");
        return 220;
    }
}

/**
 * 充电电压接口
 */
public interface Input5v {
    int input5v();
}

/**
 * 手机充电输入电压5v
 */
public class Voltage5v implements Input5v {
    public int input5v() {
        System.out.println("手机充电电压5v");
        return 5;
    }
}

/**
 * 5v正常充电
 */
public class phone {

    public void charge(Input5v input5v){
        if (input5v.input5v()==5){
            System.out.println("充电成功");
        }else {
            System.out.println("充电失败");
        }

    }
}


//接下来是重点了,适配器将220v转化为5v
public class VoltageAdapter implements Input5v {

    //220v电压
    private Voltage220v voltage220v;

    public VoltageAdapter(Voltage220v voltage220v) {
        this.voltage220v = voltage220v;
    }

    public int input5v() {
        int original = voltage220v.output220v();
        System.out.println("原电压为:"+original+"v");
        int current = original / 44;
        System.out.println("转换后的电压为:"+current+"v");
        return current;
    }
}

测试

phone phone = new phone();
//适配器将220v转化为5v
VoltageAdapter voltageAdapter = new VoltageAdapter(new Voltage220v());
//充电
phone.charge(voltageAdapter);

具体应用:io流

//字节流
FileInputStream fileInputStream = new FileInputStream("D:\\spring-context.xml");
//典型的适配器模式,将字节流转化为字符流
InputStreamReader reader = new InputStreamReader(fileInputStream);
//字符流
BufferedReader bufferedReader = new BufferedReader(reader);
String s = bufferedReader.readLine();
System.out.println(s);

8 模板方法模式

一个抽象类中定义了执行它的方法的方式,它的子类可以按照需要重写方法实现,但调用将以抽象类中定义的方法进行。

举个例子:要开发一个游戏,游戏分为三个部分,初始化,开始游戏,结束游戏

public abstract class Game {
   
    //初始化
    abstract void initialze();
    //开始游戏
    abstract void startPlay();
    //结束游戏
    abstract void endPlay();

    /**
     * 具体业务逻辑由子类实现
     * 这里只是定义了方法的执行方式
     */
    public void play(){
        initialze();
        startPlay();
        endPlay();
    }
}

spring源码中BeanFactory就是典型的模板方法设计模式

9 代理模式

代理分为静态代理和动态代理,静态代理非常简单,这里就不说了,这里重点是动态代理。

动态代理其实就是在程序运行过程中动态的生成代理类,代理类的生成方式无非就是通过字符串拼接代理类内容和io流生成一个代理类文件,然后使用类加载器加载到jvm中,最后使用反射生成代理类对象。(真实的其实不是这样,jdk动态代理生成的代理类是通过asm框架直接操作字节码文件实现的,节省了编译过程,效率更好)

动态代理有两种实现方式,一种是jdk动态代理,还有一种是cglib动态代理。

9.1 jdk动态代理

jdk实现代理的方式是代理类和被代理类要实现同一个接口

//共同接口
public interface Subject {
     void sell();
}

//被代理类
public class User implements Subject {

    public void sell(){
        System.out.println("买书");
    }
}

生成代理类

final User user = new User();
//这个方法返回代理类对象
Subject subject = (Subject) Proxy.newProxyInstance(
    //使用被代理类的类加载器
    User.class.getClassLoader(),
    //共同接口
    new Class[]{Subject.class},
    //与代理类关联的调用逻辑,内部类
    new InvocationHandler() {
        /**
         * 这个方法处理代理类的业务逻辑
         * 此处就是什么时候打印日志
         *
         * @param proxy  代理类对象
         * @param method 被代理的方法的对象
         * @param args   被代理的方法的参数
         * @return 返回被代理类方法的执行结果
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //实现业务逻辑,比如日志
            System.out.println("日志打印");
            //执行被代理类的被代理方法
            return method.invoke(user, args);
        }
    });

subject.sell();

生成的代理类

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    /**
     * 静态代码块,对象创建的时候执行
     */
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            //m3是公共接口中sell方法的对象
            m3 = Class.forName("cn.lx.proxy.jdk.Subject").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }


    /**
     * 代理类的sell方法
     */
    public final void sell() throws  {
        try {
            //h是你传入的InvocationHandler对象
            //执行该对象的invoke方法
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

保存代理类

//加入该行代码可使代理类保存到项目根路径下
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //设置系统属性

9.2 cglib动态代理

cglib相比于jdk而言简单一些。它的原理就是继承,重写父类方法。

下面我使用cglib实现上面的相似的代理。

需要导包

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

被代理类

public class User {
    public void sell(){
        System.out.println("买书");
    }
}

生成代理类

//被代理类对象
final User user = new User();

//增强器
Enhancer e = new Enhancer();
//设置被代理类的父类
e.setSuperclass(User.class);

//设置回调(方法拦截器)
e.setCallback(new MethodInterceptor() {
    /**
     * All generated proxied methods call this method instead of the original method.
     * The original method may either be invoked by normal reflection using the Method object,
     * or by using the MethodProxy (faster).
     *
     * @param obj    "this", the enhanced object
     * @param method intercepted Method
     * @param args   argument array; primitive types are wrapped
     * @param proxy  used to invoke super (non-intercepted method); may be called
     *               as many times as needed
     * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
     * @throws Throwable any exception may be thrown; if so, super method will not be invoked
     * @see MethodProxy
     */
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("代理前");
        //执行被代理类的被代理方法
        Object invoke = method.invoke(user, args);
        System.out.println("代理后");
        return invoke;
    }
});
//创建代理类
User proxy = (User) e.create();
//调用
proxy.sell();

我们看一下生成的代理类源码(省略equals,tostring,hashcode clone方法)

public class User$$EnhancerByCGLIB$$fb483fa1 extends User implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$sell$0$Method;
    private static final MethodProxy CGLIB$sell$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;


    /**
     * 静态代码块,创建对象的时候会调用
     */
    static {
        CGLIB$STATICHOOK1();
    }

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        //获取代理类的class对象
        Class var0 = Class.forName("cn.lx.proxy.cglib.User$$EnhancerByCGLIB$$fb483fa1");
        //object类的方法对象
        Class var1;
        //这里是获取equals,tostring,hashcode clone 的方法对象
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        //获取user类sell方法的对象
        CGLIB$sell$0$Method = ReflectUtils.findMethods(new String[]{"sell", "()V"}, (var1 = Class.forName("cn.lx.proxy.cglib.User")).getDeclaredMethods())[0];
        CGLIB$sell$0$Proxy = MethodProxy.create(var1, var0, "()V", "sell", "CGLIB$sell$0");
    }

    final void CGLIB$sell$0() {
        super.sell();
    }

    public final void sell() {
        //获取方法拦截器,通过enhancer创建代理类的时候,会将方法拦截器(回调函数)
        //注入到代理对象中
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            //这里是重点,执行方法拦截器的intercept方法
            var10000.intercept(this, CGLIB$sell$0$Method, CGLIB$emptyArgs, CGLIB$sell$0$Proxy);
        } else {
            super.sell();
        }
    }

    /**
     * 这个方法作用是获取回调函数
     *
     * @param var0
     */
    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        User$$EnhancerByCGLIB$$fb483fa1 var1 = (User$$EnhancerByCGLIB$$fb483fa1) var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor) ((Callback[]) var10000)[0];
        }

    }


    /**
     * 通过enhancer设置回调函数,也就是方法拦截器
     *
     * @param var1
     * @param var2
     */
    public void setCallback(int var1, Callback var2) {
        switch (var1) {
            case 0:
                this.CGLIB$CALLBACK_0 = (MethodInterceptor) var2;
            default:
        }
    }

}

保存代理类

//加入该行代码可使代理类保存到c盘class路径下
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
 //设置系统属性

未完待续。。。。。。。。。。。。

上一篇:【Java】Java中的动态代理以及在框架中的应用


下一篇:代理模式