在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支持两种类型的代理:
如何创建和使用代理对象?
代码结构
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的规则。
- 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>
|
- 单个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]权限 异常。
代理模式的体现
若依框架的真实逻辑补充
若依框架中实际的权限拦截核心类是 PermissionAspect(路径:com.ruoyi.framework.aspectj.PermissionAspect),核心差异:
权限获取:从 SecurityUtils.getLoginUser() 获取当前登录用户,再关联查询角色和菜单权限;
异常处理:不直接抛 RuntimeException,而是封装为 AjaxResult.error(“权限不足”);
扩展能力:支持多权限(如 @RequiresPermission({“system:user:list”,“system:user:query”}))。
总结
核心原理:Spring AOP 基于动态代理模式,通过切面拦截目标方法,实现无侵入的权限校验,这是若依框架权限控制的核心;
关键步骤:自定义权限注解 → 定义切入点 → 环绕通知做权限校验 → 放行 / 拦截目标方法;
代理类型:目标类有接口用 JDK 动态代理,无接口用 CGLIB 动态代理,Spring 自动适配。
文章参考: