ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

23种设计模式总结

2020-12-20 18:02:17  阅读:125  来源: 互联网

标签:总结 23 void class static CGLIB new 设计模式 public


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");
 //设置系统属性

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

标签:总结,23,void,class,static,CGLIB,new,设计模式,public
来源: https://blog.csdn.net/qq_33012981/article/details/111456634

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有