目录

什么是代理对象?

在Spring框架中,代理对象是一个非常重要的概念,特别是在使用依赖注入(Dependency Injection)和面向切面编程(AOP,Aspect-Oriented Programming)时。下面我将详细解释什么是代理对象,以及在Spring中如何创建和使用代理对象。

SpringAOP的核心首先是创建代理对象存入SpringIoC容器中,然后就是调用的时候使用责任链模式进行调用。首先研究SpringAOP创建代理对象的机制。

在Spring的AOP实现中,使用的核心技术是代理技术,而这种动态代理实际上是JDK的一个特性(1.3)。基于JDK动态代理的target目标对象必须实现接口。如果没有实现的接口需要基于Cglib代理。当然可以使用@EnableAspectJAutoProxy(proxyTargetClass  = true) 强转使用Cglib代理。

在Java社区里AspectJ是最完整的AOP框架,但SpringAOP也提供了另外一种实现,这种实现并不是AspectJ的竞争者,相反,SpringAOP还将AspectJ集成了进来,为IoC容器和Spring应用开发提供了一个一致性的AOP解决方案。

SpringAOP的核心技术是代理技术。以动态代理技术为基础,设计出了一系列AOP的横切实现,比如前置通知、返回通知、异常通知等。同时,SpringAOP还提供了一系列的Pointcut来匹配切入点,可以使用现有的切入点来设计横切面,也可以扩展相应的Pointcut来实现切入需求。(我们可以看到好多aspectj包相关的东西,其实Spring只是引用了AspectJ的语法,其实现核心还是基于代理以及责任链模式来实现代理链条调用)。

在SpringAOP中,对于AOP的使用者来说,只需要配置相关的bean。但是为了能让AOP起作用,需要完成一系列的过程,比如:为目标对象建立代理对象,这个代理对象可以用JDK代理,也可以用CGLIB代理;然后还需要启动代理对象的拦截器来完成切面的织入,这一系列的织入设计是通过一系列Adapter来实现的。通过Adapter的设计,可以把AOP的横切面设计和Proxy模式有机地结合起来,从而实现在AOP中定义好的各种织入方式。

什么是代理对象?

代理对象是在运行时创建的对象,它实现了某个接口或者继承了某个类,并增强了该对象的功能。在Spring中,代理对象通常用于以下目的:

  • 控制访问‌:例如,通过代理对象可以控制对某些方法的访问权限。

  • 增强功能‌:在方法执行前后添加额外的功能,例如日志记录、事务管理等。

  • 实现接口‌:当使用接口编程时,可以通过代理来实现该接口,这在AOP中尤其常见。

特性 静态代理 JDK动态代理 CGLIB动态代理
依赖条件 需与目标对象实现相同接口 目标对象必须实现接口 目标对象无需实现接口
创建时机 编译期生成代理类 运行期动态生成代理类 运行期动态生成代理子类
性能 无反射开销,性能高 有反射开销,性能中等 无反射,基于字节码,性能高
适用场景 目标类少、方法固定 接口驱动的业务场景 无接口的类、第三方类库

Spring中的代理方式

Spring支持两种类型的代理:

  • 基于接口的代理‌(JDK动态代理):通过实现接口来创建代理对象。这是通过Java的动态代理机制实现的,适用于有接口的情况。

  • 基于类的代理‌(CGLIB代理):通过继承类来创建代理对象。这是通过CGLIB库实现的,适用于没有接口或者需要代理类的情况。

如何创建和使用代理对象?

代码结构

1
2
3
4
5
6
7
8
9
src/main/java/com/example/proxy/
├── service/
│   ├── UserService.java         // 目标接口
│   └── UserServiceImpl.java     // 目标实现类
├── proxy/
│   ├── UserServiceStaticProxy.java  // 静态代理类
│   ├── UserServiceDynamicProxy.java // JDK动态代理工厂
│   └── UserServiceCglibProxy.java   // CGLIB动态代理工厂
└── ProxyDemo.java                // 测试类

目标接口

1
2
3
4
5
6
package com.example.proxy.service;

public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

目标实现类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.example.proxy.service;

public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("【核心业务】添加用户: " + username);
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("【核心业务】删除用户: " + username);
    }
}

静态代理

UserServiceStaticProxy 静态代理类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.example.proxy.proxy;

import com.example.proxy.service.UserService;

// 静态代理类:与目标对象实现相同接口
public class UserServiceStaticProxy implements UserService {
    // 持有目标对象引用
    private final UserService target;

    public UserServiceStaticProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void addUser(String username) {
        // 增强逻辑:前置日志
        System.out.println("【静态代理-前置】记录添加用户操作,入参: " + username);
        // 调用目标对象方法
        target.addUser(username);
        // 增强逻辑:后置日志
        System.out.println("【静态代理-后置】添加用户操作完成");
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("【静态代理-前置】记录删除用户操作,入参: " + username);
        target.deleteUser(username);
        System.out.println("【静态代理-后置】删除用户操作完成");
    }
}

jdk动态代理

UserServiceDynamicProxy 动态代理工厂

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.example.proxy.proxy;

import com.example.proxy.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// JDK动态代理工厂:基于接口生成代理对象
public class UserServiceDynamicProxy {
    // 目标对象
    private final Object target;

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

    // 生成代理对象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),  // 目标类类加载器
                target.getClass().getInterfaces(),    // 目标类实现的接口
                new InvocationHandler() {             // 调用处理器
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 增强逻辑:前置日志 + 权限校验
                        System.out.println("【JDK动态代理-前置】方法: " + method.getName() + ", 入参: " + (args != null ? args[0] : ""));
                        System.out.println("【JDK动态代理-前置】权限校验通过");
                        // 调用目标对象方法
                        Object result = method.invoke(target, args);
                        // 增强逻辑:后置日志
                        System.out.println("【JDK动态代理-后置】方法执行完成");
                        return result;
                    }
                }
        );
    }
}

ccglib代理

cglib的流程大体是创Enhancer对象,然后设置其父类,设置回调(回调是个Callback对象,也可以传递数组。实际net.sf.cglib.proxy.MethodInterceptor 继承自Callback接口)。单个Callback不需要传递设置CallbackFilter,多个Callback需要设置CallbackFilter( 内部accept方法返回Cabblack数组的下标),让Cglib知道获取Callback的规则。

  1. pom依赖
1
2
3
4
5
6
    <!-- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.12</version>
    </dependency>
  1. 单个Callback的测试:

UserServiceCglibProxy.java(CGLIB 动态代理工厂)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.example.proxy.proxy;

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

// CGLIB动态代理工厂:基于类继承生成代理对象(无需接口)
public class UserServiceCglibProxy implements MethodInterceptor {
    // 目标对象
    private final Object target;

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

    // 生成代理对象
    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        // 设置父类为目标类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 增强逻辑:前置日志 + 耗时统计
        long startTime = System.currentTimeMillis();
        System.out.println("【CGLIB动态代理-前置】方法: " + method.getName() + ", 入参: " + (args != null ? args[0] : ""));
        // 调用目标对象方法
        Object result = methodProxy.invokeSuper(o, args);
        // 增强逻辑:后置耗时统计
        long endTime = System.currentTimeMillis();
        System.out.println("【CGLIB动态代理-后置】方法耗时: " + (endTime - startTime) + "ms");
        return result;
    }
}

测试类 ProxyDemo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.proxy;

import com.example.proxy.proxy.UserServiceCglibProxy;
import com.example.proxy.proxy.UserServiceDynamicProxy;
import com.example.proxy.proxy.UserServiceStaticProxy;
import com.example.proxy.service.UserService;
import com.example.proxy.service.UserServiceImpl;

public class ProxyDemo {
    public static void main(String[] args) {
        // 1. 原始对象调用
        System.out.println("===== 原始对象 =====");
        UserService userService = new UserServiceImpl();
        userService.addUser("zhangsan");

        // 2. 静态代理调用
        System.out.println("\n===== 静态代理 =====");
        UserService staticProxy = new UserServiceStaticProxy(userService);
        staticProxy.addUser("lisi");

        // 3. JDK动态代理调用
        System.out.println("\n===== JDK动态代理 =====");
        UserServiceDynamicProxy dynamicProxyFactory = new UserServiceDynamicProxy(userService);
        UserService dynamicProxy = (UserService) dynamicProxyFactory.getProxyInstance();
        dynamicProxy.deleteUser("wangwu");

        // 4. CGLIB动态代理调用
        System.out.println("\n===== CGLIB动态代理 =====");
        UserServiceCglibProxy cglibProxyFactory = new UserServiceCglibProxy(userService);
        UserService cglibProxy = (UserService) cglibProxyFactory.getProxyInstance();
        cglibProxy.addUser("zhaoliu");
    }
}

结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
===== 原始对象 =====
【核心业务】添加用户: zhangsan

===== 静态代理 =====
【静态代理-前置】记录添加用户操作,入参: lisi
【核心业务】添加用户: lisi
【静态代理-后置】添加用户操作完成

===== JDK动态代理 =====
【JDK动态代理-前置】方法: deleteUser, 入参: wangwu
【JDK动态代理-前置】权限校验通过
【核心业务】删除用户: wangwu
【JDK动态代理-后置】方法执行完成

===== CGLIB动态代理 =====
【CGLIB动态代理-前置】方法: addUser, 入参: zhaoliu
【核心业务】添加用户: zhaoliu
【CGLIB动态代理-后置】方法耗时: 0ms

Sping AOP 动态代理实践

结合若依框架权限拦截场景的 Spring AOP 基于代理模式的实战案例

先理解核心背景

代理模式:Spring AOP 的底层核心就是动态代理(JDK 动态代理 + CGLIB 动态代理),目标是在不修改目标方法代码的前提下,对方法进行增强(如权限校验、日志记录)。

若依框架权限拦截场景:若依框架中最典型的 AOP 应用是 @PreAuthorize/ 自定义 @DataScope/@RequiresPermissions 这类权限注解的拦截,通过 AOP 拦截注解标注的方法,校验用户是否有对应权限,无权限则抛出异常。

实战案例实现(仿若依权限拦截)

核心思路

自定义权限注解 @RequiresPermission,标注在需要权限校验的接口方法上;

编写 AOP 切面类,通过环绕通知拦截标注了该注解的方法;

在切面中获取当前登录用户的权限列表,与注解中要求的权限对比;

权限校验通过则执行目标方法,不通过则抛出权限不足异常;

底层由 Spring 动态代理自动生成目标类的代理对象,实现无侵入增强。

完整代码实现

自定义权限注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.lang.annotation.*;

/**
 * 仿若依权限注解,标注需要权限校验的方法
 */
@Target({ElementType.METHOD}) // 仅作用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时生效,AOP才能获取
@Documented
public @interface RequiresPermission {
    /**
     * 要求的权限标识(如:system:user:list)
     */
    String value() default "";
}

编写 AOP 切面类(核心)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 权限拦截切面(仿若依框架逻辑)
 * 底层由 Spring 动态代理实现:
 * - 若目标类有接口,用 JDK 动态代理;
 * - 若目标类无接口,用 CGLIB 动态代理。
 */
@Aspect // 标识为切面类
@Component // 交给 Spring 容器管理
public class PermissionAspect {

    /**
     * 定义切入点:拦截所有标注了 @RequiresPermission 的方法
     */
    @Pointcut("@annotation(com.ruoyi.common.annotation.RequiresPermission)")
    public void permissionPointcut() {}

    /**
     * 环绕通知:在目标方法执行前后做权限校验
     */
    @Around("permissionPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 获取当前请求的上下文(仿若依获取登录用户信息)
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 模拟从请求中获取当前登录用户的权限列表(若依实际是从 Token/Shiro/SpringSecurity 中获取)
        List<String> userPermissions = getUserPermissions(request);

        // 2. 获取注解中的目标权限
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RequiresPermission annotation = method.getAnnotation(RequiresPermission.class);
        String requiredPermission = annotation.value();

        // 3. 权限校验(核心逻辑)
        if (requiredPermission == null || requiredPermission.isEmpty()) {
            // 无指定权限,直接放行
            return joinPoint.proceed();
        }
        if (!userPermissions.contains(requiredPermission)) {
            // 权限不足,抛出异常(若依实际会返回统一的错误结果)
            throw new RuntimeException("权限不足,无[" + requiredPermission + "]权限");
        }

        // 4. 权限通过,执行目标方法(代理模式的核心:调用目标对象的方法)
        Object result = joinPoint.proceed();

        // 5. 方法执行后可扩展逻辑(如日志记录)
        System.out.println("目标方法执行完成,权限校验通过");
        return result;
    }

    /**
     * 模拟获取当前用户的权限列表(若依实际是从 SysUser/SysRole/SysMenu 表中查询)
     */
    private List<String> getUserPermissions(HttpServletRequest request) {
        // 模拟数据:实际项目中替换为真实的权限查询逻辑
        List<String> permissions = new ArrayList<>();
        permissions.add("system:user:list");
        permissions.add("system:user:add");
        return permissions;
    }
}

业务层 / 控制器使用注解(仿若依接口)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 仿若依的用户管理控制器
 */
@RestController
@RequestMapping("/system/user")
public class SysUserController {

    /**
     * 查询用户列表:需要 system:user:list 权限
     */
    @RequiresPermission("system:user:list")
    @GetMapping("/list")
    public String list() {
        return "查询用户列表成功";
    }

    /**
     * 删除用户:需要 system:user:delete 权限(当前用户无此权限,会触发异常)
     */
    @RequiresPermission("system:user:delete")
    @GetMapping("/delete")
    public String delete() {
        return "删除用户成功";
    }
}

测试效果

访问 /system/user/list:用户有 system:user:list 权限,正常返回 查询用户列表成功;

访问 /system/user/delete:用户无 system:user:delete 权限,抛出 权限不足,无[system:user:delete]权限 异常。

代理模式的体现

  • JDK 动态代理:如果 SysUserController 实现了某个接口(如 UserControllerApi),Spring 会基于接口生成代理类,代理类中封装了权限校验逻辑,再调用目标类的方法;

  • CGLIB 动态代理:如果 SysUserController 无接口,Spring 会生成目标类的子类作为代理类,重写目标方法,加入权限校验逻辑。

若依框架的真实逻辑补充

若依框架中实际的权限拦截核心类是 PermissionAspect(路径:com.ruoyi.framework.aspectj.PermissionAspect),核心差异: 权限获取:从 SecurityUtils.getLoginUser() 获取当前登录用户,再关联查询角色和菜单权限;

异常处理:不直接抛 RuntimeException,而是封装为 AjaxResult.error(“权限不足”);

扩展能力:支持多权限(如 @RequiresPermission({“system:user:list”,“system:user:query”}))。

总结

核心原理:Spring AOP 基于动态代理模式,通过切面拦截目标方法,实现无侵入的权限校验,这是若依框架权限控制的核心;

关键步骤:自定义权限注解 → 定义切入点 → 环绕通知做权限校验 → 放行 / 拦截目标方法;

代理类型:目标类有接口用 JDK 动态代理,无接口用 CGLIB 动态代理,Spring 自动适配。

文章参考: