栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Java 设计模式 · 结构型 & AOP】代理模式(Proxy Pattern)& Spring AOP 应用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Java 设计模式 · 结构型 & AOP】代理模式(Proxy Pattern)& Spring AOP 应用

代理模式 Proxy Pattern & Spring AOP 应用
  • 一、概述
  • 二、结构
  • 三、实现
    • 1. 静态代理
    • 2. JDK 动态代理
    • 3. CGLib代理
  • 四、特点
    • ☯ 优点
    • ☯ 缺点
  • 五、动态代理应用:AOP
  • 六、Spring AOP
    • Spring Boot 使用 AOP

结构型模式关注如何将现有类或对象组织一起形成更加强大的结构。

一、概述

代理模式(Proxy Pattern):给某个对象提供一个代理活占位符,并由代理对象来控制对原对象的访问。

二、结构

  • Subject(抽象主题角色):
    声明了真实主题和代理的主题的共同接口,这样一来,在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
  • Proxy(代理主题角色):
    包含了对真实主题对象的引用,从而可以操作真实对象,在代理主题中提供了与真实主题相同的接口,对真实对象的使用加以控制并约束,可以在执行主题操作前后执行其他操作。
  • RealSubject(真实主题角色):
    定义了代理角色所代表的真实对象,在真实主题角色中实现了真正的业务操作,客户端可以通过代理主题角色调用真实角色中定义的操作
三、实现

我们以房屋租借问题中的房东、代理商为例:

抽象主题角色: IRent 租借接口:定义租借相关行为规范

public interface IRent {
    void rent();	//租借
}

真实主题角色: HouseKeeping:房东类

public class HouseKeeping implements IRent {
    
    @Override
    public void rent() {
        System.out.println("我有一套房,租金1500每月");
    }
}
1. 静态代理

代理主题角色: HouseAgency 租借代理商类

public class HouseAgency implements IRent {
    //真实主题对象
    private IRent rent;
    
    
    public HouseAgency(IRent rent) {
        this.rent = rent;
    }

    
    @Override
    public void rent() {
        before();
        Object result = method.invoke(this.real, args);
        after();
    }
	
	
    public void before() {
        System.out.println("提前收押金");
    }

    
    public void after() {
        System.out.println("入住收物业费、水电费...");
    }
}

测试代码:
使用 JUnit 单元测试:

@Test
public void test() {
	
    IRent rent = new HouseAgency(new HouseKeeping());
    
    rent.rent();
}

测试结果:

2. JDK 动态代理

从 JDK 1.3 开始,Java语言提供了对动态代理的支持,需要用到 java.lang.reflect 包下的一些类:

Proxy 类:

Proxy 类提供了用于创建动态代理类和实例对象的方法,它是创建动态代理类的父类,它最常用的方法如下:

public static Class getProxyClass(ClassLoader loader, Class... interfaces)


public static Object newProxyInstance(ClassLoader loader, Class... interfaces, InvocationHandler h)

InvocationHandler 接口:

代理处理程序的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler 接口的子类)

其核心方法如下:

public Object invork(Object proxy, Method method, Object[] args)

实现代码(代理主题角色):

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class HouseProxy implements InvocationHandler {
	//真实主题角色
    private Object real;
	
	
    public HouseProxy(Object real) {
        this.real = real;
    }

	
    public Object createProxy() {
        return Proxy.newProxyInstance(this.real.getClass().getClassLoader(), this.real.getClass().getInterfaces(), this);
    }

    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.real, args);
        after();
        return result;
    }

    
    public void before() {
        System.out.println("提前收押金");
    }

    
    public void after() {
        System.out.println("入住收物业费、水电费...");
    }
}

测试代码:
使用 JUnit 单元测试:

@Test
public void test(){
	
    IRent rent = (IRent) new HouseProxy(new HouseKeeping()).createProxy();
    
    rent.rent();
}

测试结果:

3. CGLib代理

CGLib (Code Generation Library) ,一个强大的、高性能、高质量的 Code 生成类库。
它可以在运行期扩展 Java 类与实现 Java 接口。Hibernate 用它来实现 PO 字节码的动态生成。CGLib 比 Java 的 java.lang.reflect.Proxy 类更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法。CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程。

JDK动态代理、CGLIB代理 区别:

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。
  • CGLIB针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。

两者速度对比:

  • JDK动态代理是面向接口,在创建代理实现类时速度比CGLib快
  • CGLib动态代理是通过字节码底层继承要代理类来实现(代理类不能被final关键字所修饰),运行速度比JDK动态代理更快

Maven 引入依赖:


    cglib
    cglib
    3.3.0

CGLib 的核心接口是位于 net.sf.cglib.proxy 包下的 MethodInterceptor 接口,它继承自 Callback 接口:

核心方法:

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable 

实现代码:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;


public class CGLibProxy implements MethodInterceptor {
	//真实主题角色
    private Object real;
	
	
    public CGLibProxy(Object real) {
        this.real = real;
    }

    
    public Object createProxy() {
        Enhancer e = new Enhancer();
        e.setSuperclass(this.real.getClass());
        e.setCallback(this);
        return e.create();
    }

	
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(obj,args);
        after();
        return result;
    }
    
    public void before() {
        System.out.println("提前收押金");
    }

    
    public void after() {
        System.out.println("入住收物业费、水电费...");
    }
}

测试代码:

@Test
public void test(){
	
	HouseKeeping h = (HouseKeeping) new CGLibProxy(new HouseKeeping()).createProxy();
	
	h.rent();
}

测试结果:

四、特点 ☯ 优点
  • 能够协调调用者与被调用者,一定程度上降低了系统的耦合度
  • 可针对抽象主题角色进行编程,更换代理类灵活、便于扩展,符合开闭原则
☯ 缺点
  • 由于代理对象的出现,可能使得处理请求变慢
五、动态代理应用:AOP

AOP(Aspect-OrientedProgramming,面向切面编程),AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP的应用技术:

  1. 采用动态代理技术,截获消息进行装饰,取代原有对象(真实主题角色)行为的执行
  2. 采用静态织入方式,在编译期间织入相关代码
六、Spring AOP

Spring 动态代理机制:
Spring 默认提供了两种方式来生成代理对象:JDK Proxy + CGLib,具体使用哪种根据情况而定,默认策略如下:

  • 目标类是接口,使用 JDK动态代理
  • 目标对象没有实现接口,采用 CGLIB代理

Spring 提供了配置参数来强制选择使用 CGLIB 技术,如下:


CGLIB使用生成代理子类实现代理,proxy-target-class表示属性值决定基于接口 / 类的代理被创建,proxy-target- 表示 强制使用 CGLIB 技术来实现AOP,若填入 配置缺省,则依据 Spring 默认策略 选择代理。

相关术语:

  • 通知(Advice):包含了需要用于多个应用对象的横切行为
  • 连接点(Join Point):程序执行过程中能够应用通知的所有点
  • 切点(PointCut):定义何时进行切入,哪些连接点会得到通知
  • 切面(Aspect):通知、切点相结合
  • 引入(Introduction):允许向现有类中添加新的属性、方法
  • 织入(Weaving):将切面应用到目标对象,并创建新代理对象的过程,分为编译期织入、类加载期织入、运行期织入
Spring Boot 使用 AOP

在 SpringBoot 中使用 AOP 之前,首先要引入相关依赖:
使用 Maven 引入依赖:


    org.springframework.boot
    spring-boot-starter-aop
    2.5.4

我们写一个小的demo,对AOP进行测试:

核心代码结构一览:

编写 Controller 并对其进行切面:
IDEA 对 AOP 支持度较高,被切面的方法,会在以特殊图标进行标记:

编写 Controller 代码:

package com.ljw.aop.controller;		//包路径
//省略imports...

@RestController
@RequestMapping("/api/aop")
public class AopController {

    @GetMapping("/hello")
    public String hello(){
        System.out.println("hello");
        return "hello";
    }
}

定义切点:

切点是通过@Pointcut注解和切点表达式定义的,@Pointcut注解可以在一个切面内定义可重用的切点。

Spring 切面力度最小可达到方法级别,使用 execution 表达式需致命方法返回类型、类名、方法名、参数名等相关信息,这种使用方式最为广泛:

定义通知:

五种通知类型(包括 IDEA 支持图标):

  • 前置(@Before):目标方法调用前执行通知

  • 后置(@After):目标方法调用后执行通知

  • 环绕(@Around):目标方法调用前后执行通知

  • 返回(@AfterReturning):目标方法成功执行之后执行通知

  • 异常(@AfterThrowing):目标方法抛出异常之后执行通知

编写 Advice 代码:

package com.ljw.aop.advice;		//包路径
//省略imports...

@Aspect
@Component
public class AopAdvice {

    
    @Pointcut("execution (* com.ljw.aop.controller.*.*(..))")
    public void point() {

    }

    
    @Before("point()")
    public void before() {
        System.out.println("before");
    }

    
    @After("point()")
    public void after() {
        System.out.println("after");
    }

    
    @Around("point()")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("around-before");    //执行前
        try {
            proceedingJoinPoint.proceed();      //执行时
        } catch (Throwable t) {
            t.printStackTrace();
        }
        System.out.println("around-after");     //执行后
    }
    
    
    @AfterReturning("point()")
    public void returnAdvice() {
        System.out.println("finish");
    }
	
	
    @AfterThrowing("point()")
    public void throwAdvice() {
        System.out.println("Exception!!!");
    }
}

访问路径:

访问成功:

访问后结果:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/274573.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号