经典设计模式之代理模式【买房找中介】

一、什么是代理模式

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

二、为什么要使用代理模式

中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

开闭原则,增加功能代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

三、代理模式实现原理

代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy),如下图所示:
经典设计模式之代理模式【买房找中介】_第1张图片

1、抽象主题角色(Subject):可是是接口,也可以是抽象类。
2、委托类角色(Proxied):真实主题角色,业务逻辑对具体执行者。
3、代理类角色(Proxy):内部含有对真实对RealSubject的饮用,负责对真实主题角色对调用,并在真实主题角色处理前后做预处理和后处理。

四、代理模式应用场景

SpringAop、日志收集、权限控制、过滤器、RPC远程调用

五、代理模式的创建方式

代理模式的创建方式有两种,分钟静态代理和动态代理,其中动态代理又包含JDK动态代理和CGLIB动态代理。

静态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。
所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
一句话,自己手写代理类就是静态代理。

基于实现接口的方式实现
public interface OrderService {
     
    /**
     * 共同的抽象方法
     */
    void order();
}

/**
 * 实现类
 */
public class OrderServiceImpl implements OrderService {
     
    public void order() {
     
        System.out.println("执行订单业务逻辑代码...");
    }
}

/**
 * 代理类
 */
public class OrderServiceProxy implements OrderService {
     
    private OrderService orderService;

    public OrderServiceProxy(OrderService orderService){
     
        this.orderService = orderService;
    }

    public void order() {
     
        System.out.println("打印订单日志开始");
        orderService.order();
        System.out.println("打印订单日志结束");
    }
}

/**
 * 测试类
 */
public class ProxyClient {
     
    public static void main(String[] args) {
     
        OrderServiceProxy orderServiceProxy = new OrderServiceProxy(new OrderServiceImpl());
        orderServiceProxy.order();
    }
}
基于继承的方式实现
/**
 * 代理类
 */
public class OrderServiceProxy extends OrderServiceImpl {
     
    public void order() {
     
        System.out.println("打印订单日志开始");
        super.order();
        System.out.println("打印订单日志结束");
    }
}

/**
 * 测试类
 */
public class ProxyClient {
     
    public static void main(String[] args) {
     
        OrderServiceProxy orderService = new OrderServiceProxy();
        orderService.order();
    }
}

动态代理

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成。

JDK动态代理

JDK动态代理的一般步骤如下:
1.创建被代理的接口和类;
2.实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;
3.调用Proxy的静态方法,创建代理类并生成相应的代理对象;

/**
 * 主题Subject
 */
public interface OrderService {
     
    /**
     * 共同的抽象方法
     */
    void order();
}

/**
 * 实现类
 */
public class OrderServiceImpl implements OrderService {
     
    public void order() {
     
        System.out.println("执行订单业务逻辑代码...");
    }
}

/**
 * 代理类
 */
public class JdkInvocationHandler implements InvocationHandler {
     
    /**
     * 被代理对象,目标对象
     */
    private Object target;

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

    /**
     *
     * @param proxy 代理类对象
     * @param method 被代理执行的方法
     * @param args 目标方法参数
     * @return 方法执行的返回值
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     
        System.out.println("日志收集开始。。。");
        Object result = method.invoke(target, args);
        System.out.println("日志收集结束。。。");
        return result;
    }

    /**
     * 生成代理类对象
     * @param 
     * @return
     */
    public <T> T getProxy(){
     
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces() , this);
    }
}

/**
 * 测试类
 */
public class JdkProxyClient {
     
    public static void main(String[] args) {
     
        JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());
        OrderService orderService = jdkInvocationHandler.getProxy();
        orderService.order();
    }
}
原理分析

1、获取代理生成的class文件
在测试类上面加上这段代码,就会生成代理类的字节码文件:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

代理类$Proxy0.class
2、使用反编译工具编译该class文件经典设计模式之代理模式【买房找中介】_第2张图片

注意:继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

CGLIB动态代理

Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。

CGLIB原理

运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。

CGLIB优缺点

优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快
缺点:对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数。

CGLIB实现代理

实现了MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。intercept方法具体的参数有:
1、obj : 目标类的实例
2、method :目标方法实例(通过反射获取的目标方法实例)
3、args : 目标方法的参数
4、proxy : 代理类的实例
该方法的返回值就是目标方法的返回值。

/**
 * 实现类
 */
public class OrderServiceImpl{
     
    public void order() {
     
        System.out.println("执行订单业务逻辑代码...");
    }
}


public class CglibMethodInterceptor implements MethodInterceptor {
     
    /**
     *
     * @param obj 目标类的实例
     * @param method 目标实例方法
     * @param args 目标方法的参数
     * @param proxy 代理类的实例
     * @return
     * @throws Throwable
     */
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
     
        System.out.println("日志收集开始。。。");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("日志收集结束。。。");
        return result;
    }
}

/**
 * 测试类
 */
public class CglibProxyClient {
     
    public static void main(String[] args) {
     
        CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor();
        Enhancer enhancer = new Enhancer();
        //设置代理类的父类
        enhancer.setSuperclass(OrderServiceImpl.class);
        //设置回调对象
        enhancer.setCallback(cglibMethodInterceptor);
        //创建代理对象
        OrderServiceImpl orderServiceImpl = (OrderServiceImpl)enhancer.create();
        orderServiceImpl.order();
    }
}
Maven依赖
<dependencies>
    <dependency>
        <groupId>cglibgroupId>
        <artifactId>cglibartifactId>
        <version>3.2.12version>
    dependency>
dependencies>

六、静态代理与动态代理的区别

静态代理需要自己写代理类,而动态代理不需要写代理类。

七、JDK动态代理与CGLIB实现区别

JDK动态代理底层实现:
JDK的动态代理使用Java的反射技术生成动态代理类,只能代理实现了接口的类, 没有实现接口的类不能实现动态代理。

CGLIB动态代理底层实现:
运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,不需要被代理类对象实现接口,从而CGLIB动态代理效率比Jdk动态代理反射技术效率要高。

八、使用AOP拦截Controller所有请求日志

Maven依赖信息
<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.1.1.RELEASEversion>
parent>
<dependencies>
	<dependency>
	    <groupId>cglibgroupId>
	    <artifactId>cglibartifactId>
	    <version>3.2.12version>
	dependency>
	
	<dependency>
	    <groupId>org.springframework.bootgroupId>
	    <artifactId>spring-boot-starter-webartifactId>
	dependency>
	<dependency>
	    <groupId>org.projectlombokgroupId>
	    <artifactId>lombokartifactId>
	    <version>1.16.10version>
	dependency>
	<dependency>
	    <groupId>commons-langgroupId>
	    <artifactId>commons-langartifactId>
	    <version>2.6version>
	dependency>
	<dependency>
	    <groupId>org.springframework.bootgroupId>
	    <artifactId>spring-boot-starter-aopartifactId>
	dependency>
dependencies>
@RestController
public class IndexController {
     
    @RequestMapping("/getUser")
    public String getUser(String name) {
     
        return "seccess-user:" + name;
    }
}
@Aspect
@Component
@Slf4j
public class AopLogAspect {
     
    // 申明一个切点 里面是 execution表达式
    @Pointcut("execution(* com.xwhy.controller.*.*(..))")
    public void serviceAspect(){
     

    }

    //请求method前打印内容
    @Before(value = "serviceAspect()")
    public void methodBefore(JoinPoint joinPoint){
     
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //打印请求内容
        log.info("==================请求内容==================" );
        log.info("请求地址:"+request.getRequestURL().toString());
        log.info("请求方式:" + request.getMethod());
        log.info("请求类方法:" + joinPoint.getSignature());
        log.info("请求类方法参数:" + Arrays.toString(joinPoint.getArgs()));
        log.info("===============请求内容===============");
    }

    // 在方法执行完结后打印返回内容
    @AfterReturning(returning = "obj", pointcut = "serviceAspect()")
    public void methodAfterReturing(Object obj) {
     
        log.info("--------------返回内容----------------");
        log.info("Response内容:" + obj.toString());
        log.info("--------------返回内容----------------");

    }
}

启动服务,请求:http://localhost:8080/getUser?name=mark
打印结果:

2019-05-26 14:39:27.393  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : ==================请求内容==================
2019-05-26 14:39:27.393  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : 请求地址:http://localhost:8080/getUser
2019-05-26 14:39:27.393  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : 请求方式:GET
2019-05-26 14:39:27.395  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : 请求类方法:String com.xwhy.controller.IndexController.getUser(String)
2019-05-26 14:39:27.395  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : 请求类方法参数:[mark]
2019-05-26 14:39:27.395  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : ===============请求内容===============
2019-05-26 14:39:27.399  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : --------------返回内容----------------
2019-05-26 14:39:27.400  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : Response内容:seccess-user:mark
2019-05-26 14:39:27.400  INFO 84308 --- [nio-8080-exec-1] com.xwhy.aop.AopLogAspect                : --------------返回内容----------------

你可能感兴趣的:(javase,设计模式,设计模式,代理设计模式)