装饰器模式、代理模式与AOP

什么是代理模式

定义:代理模式给个目标对象(target)提供代理对象(proxy),并由代理对象控制对目标对象的引用。
举个生活中的例子:小孩子去医院看病时,由于小朋友无法很好的描述病情,会由父母来代替向医生描述病情。这里的父母就相当于一个代理对象,小朋友相当于目标对象。医生直接访问的父母,来获取小朋友的病情。

静态代理

个人理解静态代理模式类似于装饰器模式,都是在不修改target的前提下,对target类进行扩展。
举例如下:
创建一个表示能力的接口move,两个target类BirdPlane实现能力,一个代理类StaticProxy表示对target类能力进行扩展。
在这里插入图片描述

// 表示能力的接口
public interface Move {
     void fly(Long ms);
     void run(long ms);
}
// target类实现能力
public class Bird implements Move {

    @Override
    public void fly(Long ms) {
        System.out.println("bird is flying!");
        try {
            Thread.sleep(ms);
        }
        catch (Exception e) {
            System.err.println("err");
        }
    }

    @Override
    public void run(long ms) {
        System.out.println("bird is running!");
        try {
            Thread.sleep(ms);
        }
        catch (Exception e) {
            System.err.println("err");
        }
    }
}
// 静态代理类,对原target类能力进行拓展
public class StaticProxy implements Move {

    private Move move;

    public StaticProxy(Move move) {
        this.move = move;
    }

    @Override
    public void fly(Long ms) {
        try {
            System.out.println("现在开始,我要飞了!");
            long begin = System.currentTimeMillis();
            move.fly(ms);
            long end = System.currentTimeMillis();
            System.out.println("好累哦!飞了" + (end - begin) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("invoke failed!");
            throw e;
        }
    }

    @Override
    public void run(long ms) {
        try {
            System.out.println("现在开始,我要跑了!");
            long begin = System.currentTimeMillis();
            move.fly(ms);
            long end = System.currentTimeMillis();
            System.out.println("好累哦!跑了" + (end - begin) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("invoke failed!");
            throw e;
        }
    }
}

调用代理类结果:
在这里插入图片描述
可以看到,静态代理类通过实现target类相同的接口,来进行能力扩展。也就是说,如果我们的接口新增其他的方法,target类和proxy类都需要新增实现,代码量会增加。

动态代理

动态代理通过了反射的方式,将接口中声明的所有方法都在代理类中统一处理,规避了静态代理新增方法增加代码量的问题。

JDK动态代理

jdk提供了接口InvocationHandler来实现对接口的动态代理:

使用方式

代理类:

public class JdkDynamicProxy implements InvocationHandler {

    // 被代理类的目标接口
    private Object targetObject;

    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
//        loader: 代理类的类加载器,获取加载器的方法是固定的
//        interfaces: 代理类要实现的接口列表
//        h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    /**
     * 对目标方法统一进行功能扩展
     * 
     * @param proxy 调用该方法的代理实例
     * @param method 需要调用的方法
     * @param args 方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result;
        try {
            System.out.println("现在开始,我要进行一个" + method.getName());
            long begin = System.currentTimeMillis();
            // 调用被代理方法
            result = method.invoke(targetObject, args);
            long end = System.currentTimeMillis();
            System.out.println("好累哦!" + method.getName() + "了" + (end - begin) + "ms");
        }
        catch (Exception e) {
            e.printStackTrace();
            System.out.println("invoke failed!");
            throw e;
        }
        return result;
    }
}

调用代理类结果:
在这里插入图片描述
可以看到,动态代理可以做到一个代理类,代理多个接口,每个接口的多个方法

从原理解释只能代理接口

查看运行后,生成的代理类:
$Proxy:生成的代理对象
Proxy:提供创建动态代理类的方法
Move:被代理的目标接口
运行后生成的代理类
可以看出,生成的代理类是继承了Proxy类,而Java是单继承,所以只能代理接口,而不能代理另一个类。

cglib动态代理

那么,如何实现对一个类的代理呢?Spring框架提供了cglib来实现。

使用方式

继续开头的小孩子无法说明自己病情的例子。有目标类Kid,仅有字段symptom用于表示症状,无描述病情的方法:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Kid {
    private String symptom;
}

通过实现MethodInterceptor接口来实现动态代理:

public class CglibProxy implements MethodInterceptor {

    private Object target;

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

    public Object getProxyInstance() {
        Enhancer en = new Enhancer();
        en.setSuperclass(this.target.getClass());
        en.setCallback(this);
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 在调用方法前做什么
        System.out.println("回忆孩子有什么症状...");
        // 调用原有方法
        Object returnValue = method.invoke(target, objects);
        // 调用方法后做什么
        System.out.println("获取到症状为:" + returnValue);
        // 修改返回值
        return "这孩子" + returnValue + "得睡不着啊";
    }
}

实现结果:
在这里插入图片描述
拦截了get方法,能实现在调用方法前后操作,并且能修改返回值。

AOP

在某个方法前后额外做些事情,这是不是有点什么既视感?在Spring中AOP的实现就是基于动态代理。加入容器的目标对象有实现接口,就使用JDK代理。如果没有实现接口,就使用Cglib代理。
在了解AOP前,需要知道以下术语:

  • 切点(point cut):说明在什么位置执行增强逻辑。表示被拦截的一个或多个方法,可以通过正则式和指示器来指定。
  • 通知(advice):说明在什么时候执行增强逻辑。可以用注释来指定:前置通知(@Before)、后置通知(@After)、环绕通知。
    (@Around)、事后返回通知(@After-returning)和异常通知(@After-throwing)
  • 切面(aspect):用来定义切点、各类通知和引入的内容。
  • 引入(introduction):引入新的类和其方法,增强现有Bean的功能 。
  • 织入(weaving):为原有服务对象生成代理对象 , 然后将与切点定义匹配的连接点拦截 ,并按约定将各类通知织入约定流程的过程 。

定义切面:

@Aspect
@Component
public class MoveAspect {

    @Before("execution(* com.test.springboot.proxy.service.*.*(..))")
    public void beforeMove() {
        System.err.println("before");
    }
}

定义切点,执行结果与上面相同:

@Aspect
@Component
public class MoveAspect {
	// 定义切点,方便复用
    @Pointcut("execution(* com.test.springboot.proxy.service.*.*(..))")
    public void pointCut(){}

    @Before("pointCut()")
    public void beforeMove() {
        System.err.println("before");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值