一、为什么出现代理?
咱们先抛开编程,想象一下生活中的场景。假如你是一位大明星,每天都有无数的活动邀约、采访请求,还有各种商务合作的洽谈。要是你亲自去处理这些事情,那你哪还有时间去拍戏、唱歌、提升自己的业务能力呢?所以,你就会找一个经纪人。这个经纪人就相当于你的 “代理”。
经纪人会帮你筛选合适的活动,和合作方谈合同细节,安排你的行程,在你参加活动前后帮你处理各种杂事。这样一来,你就可以把精力都放在自己擅长的演艺事业上了。
在软件开发里,代理模式的出现也是出于类似的原因:
增强功能:就像经纪人会在你参加活动前帮你做好形象设计,活动后帮你做宣传推广一样。在不改变原本代码功能的基础上,给代码添加一些额外的功能,比如记录程序运行的日志、管理数据库事务、验证用户的权限等。
解耦职责:把一些通用的功能,比如记录日志、管理事务,从主要的业务逻辑中分离出来。就好比经纪人负责处理各种杂事,你专注于演艺事业,让代码的各个部分职责更清晰,也更容易维护和复用。
控制访问:经纪人会帮你筛选合作对象,只有符合你要求的合作才会让你参与。在程序里,代理可以控制对某个对象的访问,比如在调用方法之前检查用户有没有权限。
远程调用:假如你要和国外的一个剧组合作,你不可能每次都亲自飞去国外谈合作。这时候就可以通过你的经纪人在国外的代表和剧组沟通,这个代表就相当于远程代理。在分布式系统里,代理可以隐藏网络通信的复杂细节,让你调用远程的对象就像调用本地的对象一样方便。
二、什么是代理代码?
代理代码就是实现代理模式的具体代码。在 Java 里,代理可以分为静态代理和动态代理,下面我们来详细看看。
1. 静态代理
还是以明星和经纪人为例。假如你是明星,经纪人是你的代理。静态代理就像是你提前和经纪人签好了一份详细的合同,规定了他只能帮你处理哪些活动,在活动前后要做哪些事情,而且这些规定都是固定不变的。
下面是一个简单的静态代理代码示例:
// 定义一个接口,就好比是明星要参加的活动类型
// 接口名为 Subject,里面定义了一个抽象方法 request,没有返回值
interface Subject {
// 声明一个抽象方法 request,代表要执行的操作,这里可以理解为明星要参加的活动
void request();
}
// 真实的明星类,实现了活动接口
// RealSubject 类实现了 Subject 接口,表明它可以执行接口中定义的活动
class RealSubject implements Subject {
// 重写接口中的 request 方法,实现具体的活动逻辑
@Override
public void request() {
// 打印信息,表示正在处理请求,即明星正在参加活动
System.out.println("RealSubject: Handling request.");
}
}
// 经纪人代理类,也实现了活动接口
// ProxySubject 类同样实现了 Subject 接口,作为 RealSubject 的代理类
class ProxySubject implements Subject {
// 声明一个 RealSubject 类型的私有成员变量,用于持有真实的明星对象
private RealSubject realSubject;
// 构造方法,用于接收真实的明星对象
public ProxySubject(RealSubject realSubject) {
// 将传入的真实明星对象赋值给成员变量
this.realSubject = realSubject;
}
// 重写接口中的 request 方法,在调用真实对象的方法前后添加额外逻辑
@Override
public void request() {
// 打印信息,表示在请求处理之前的操作,就像经纪人在明星参加活动前做的准备工作
System.out.println("ProxySubject: Before request.");
// 调用真实明星对象的 request 方法,让明星去参加活动
realSubject.request();
// 打印信息,表示在请求处理之后的操作,就像经纪人在明星参加活动后做的总结工作
System.out.println("ProxySubject: After request.");
}
}
// 测试类
// 用于测试静态代理的功能
public class StaticProxyTest {
// 程序的入口方法
public static void main(String[] args) {
// 创建一个真实的明星对象
RealSubject realSubject = new RealSubject();
// 创建一个经纪人代理对象,并将真实明星对象传递给它
ProxySubject proxySubject = new ProxySubject(realSubject);
// 通过经纪人代理对象调用 request 方法,触发代理逻辑
proxySubject.request();
}
}
在这个例子中,RealSubject 就像是明星本人,ProxySubject 就像是经纪人。经纪人在明星参加活动前后会做一些额外的事情,比如活动前的准备工作和活动后的总结工作。
2. 动态代理
动态代理就好比是你和经纪人的合作更加灵活。你不需要提前把所有的事情都规定好,经纪人可以根据不同的活动情况,在活动前后动态地决定要做哪些额外的事情。Java 里有两种实现动态代理的方式:JDK 动态代理和 CGLIB 动态代理。
JDK 动态代理
JDK 动态代理要求被代理的类必须实现一个或多个接口。还是以明星为例,假如明星参加的活动都有一些固定的规则(接口),经纪人就可以根据这些规则在活动前后做一些额外的事情。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义活动接口
// 接口名为 Subject,定义了一个抽象方法 request,代表明星要参加的活动
interface Subject {
// 声明一个抽象方法 request,没有返回值
void request();
}
// 真实的明星类,实现活动接口
// RealSubject 类实现了 Subject 接口,具备执行活动的能力
class RealSubject implements Subject {
// 重写接口中的 request 方法,实现具体的活动逻辑
@Override
public void request() {
// 打印信息,表示正在处理请求,即明星正在参加活动
System.out.println("RealSubject: Handling request.");
}
}
// 经纪人的调用处理器,负责处理活动前后的额外事情
// SubjectInvocationHandler 类实现了 InvocationHandler 接口,用于处理代理对象方法的调用
class SubjectInvocationHandler implements InvocationHandler {
// 声明一个 Object 类型的私有成员变量,用于持有被代理的对象
private Object target;
// 构造方法,用于接收被代理的对象
public SubjectInvocationHandler(Object target) {
// 将传入的被代理对象赋值给成员变量
this.target = target;
}
// 重写 InvocationHandler 接口的 invoke 方法,该方法会在代理对象的方法被调用时触发
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 打印信息,表示在方法调用之前的操作,就像经纪人在明星参加活动前做的准备工作
System.out.println("JDK Proxy: Before method call.");
// 通过反射调用被代理对象的方法,method 表示要调用的方法,args 表示方法的参数
Object result = method.invoke(target, args);
// 打印信息,表示在方法调用之后的操作,就像经纪人在明星参加活动后做的总结工作
System.out.println("JDK Proxy: After method call.");
// 返回方法调用的结果
return result;
}
}
// 测试类
// 用于测试 JDK 动态代理的功能
public class JdkProxyTest {
// 程序的入口方法
public static void main(String[] args) {
// 创建一个真实的明星对象
RealSubject realSubject = new RealSubject();
// 创建一个调用处理器对象,并将真实明星对象传递给它
SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);
// 使用 Proxy 类的 newProxyInstance 方法创建代理对象
// 第一个参数:类加载器,用于加载代理类,这里使用 Subject 接口的类加载器
// 第二个参数:代理类要实现的接口数组,这里只有 Subject 接口
// 第三个参数:调用处理器,用于处理代理对象方法的调用
Subject proxy = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class>[]{Subject.class},
handler
);
// 通过代理对象调用 request 方法,触发代理逻辑
proxy.request();
}
}
CGLIB 动态代理
CGLIB 动态代理不需要被代理的类实现接口,它是通过继承被代理的类来实现的。这就好比有些明星没有固定的活动规则(接口),经纪人可以直接以明星的身份去处理活动,并且在活动前后做一些额外的事情。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 真实的明星类
// RealSubject 类没有实现接口,是一个普通的类
class RealSubject {
// 定义一个方法 request,代表明星要进行的活动
public void request() {
// 打印信息,表示正在处理请求,即明星正在参加活动
System.out.println("RealSubject: Handling request.");
}
}
// 经纪人的方法拦截器,负责处理活动前后的额外事情
// SubjectMethodInterceptor 类实现了 MethodInterceptor 接口,用于拦截代理对象方法的调用
class SubjectMethodInterceptor implements MethodInterceptor {
// 重写 MethodInterceptor 接口的 intercept 方法,该方法会在代理对象的方法被调用时触发
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 打印信息,表示在方法调用之前的操作,就像经纪人在明星参加活动前做的准备工作
System.out.println("CGLIB Proxy: Before method call.");
// 通过 MethodProxy 调用父类(即被代理类)的方法
Object result = proxy.invokeSuper(obj, args);
// 打印信息,表示在方法调用之后的操作,就像经纪人在明星参加活动后做的总结工作
System.out.println("CGLIB Proxy: After method call.");
// 返回方法调用的结果
return result;
}
}
// 测试类
// 用于测试 CGLIB 动态代理的功能
public class CglibProxyTest {
// 程序的入口方法
public static void main(String[] args) {
// 创建一个 Enhancer 对象,用于生成代理类
Enhancer enhancer = new Enhancer();
// 设置要代理的类,即被代理类的父类
enhancer.setSuperclass(RealSubject.class);
// 设置回调对象,即方法拦截器,用于处理代理对象方法的调用
enhancer.setCallback(new SubjectMethodInterceptor());
// 创建代理对象
RealSubject proxy = (RealSubject) enhancer.create();
// 通过代理对象调用 request 方法,触发代理逻辑
proxy.request();
}
}
三、代理的作用
代理在软件开发中有很多重要的作用,下面我们结合生活中的例子来理解。
日志记录:就像经纪人会记录明星每次活动的时间、地点、参与人员等信息一样,代理可以在程序的方法调用前后记录日志,方便我们调试和监控程序的运行情况。
事务管理:假如明星要和一个剧组签订合同,经纪人会在合同签订前确认剧组的资金是否到位,合同签订后确保明星按照合同要求完成工作。在程序里,代理可以在方法调用前后开启和提交数据库事务,保证数据的一致性。
权限验证:经纪人会根据明星的要求,筛选合作对象。在程序里,代理可以在方法调用前检查用户是否有足够的权限访问某个功能。
缓存:如果明星经常参加某种类型的活动,经纪人可以提前准备好相关的资料。在程序里,代理可以在方法调用前检查缓存中是否已经有了需要的数据,如果有就直接返回,不用再去执行实际的方法,这样可以提高程序的性能。
四、怎么使用代理?
1. 静态代理的使用
使用静态代理就像是按照和经纪人签好的合同来办事,步骤如下:
定义一个接口,就像规定明星要参加的活动类型。
实现这个接口的真实类,就像明星本人去参加活动。
创建代理类,在代理类中持有真实类的引用,并且在调用真实类的方法前后添加额外的逻辑,就像经纪人在明星参加活动前后做一些额外的事情。
创建真实类和代理类的对象,通过代理类对象来调用方法,就像通过经纪人来安排明星的活动。
2. JDK 动态代理的使用
使用 JDK 动态代理就像是和经纪人进行灵活的合作,步骤如下:
定义接口,规定活动的规则。
实现接口的真实类,明星本人去参加活动。
创建实现 InvocationHandler 接口的调用处理器类,在 invoke 方法中添加额外的逻辑,就像经纪人根据活动情况动态地做一些额外的事情。
使用 Proxy.newProxyInstance 方法创建代理对象,就像让经纪人根据活动规则和调用处理器来安排活动。
通过代理对象调用方法,就像通过经纪人来安排明星参加活动。
3. CGLIB 动态代理的使用
使用 CGLIB 动态代理也很灵活,步骤如下:
创建真实类,明星本人。
创建实现 MethodInterceptor 接口的方法拦截器类,在 intercept 方法中添加额外的逻辑,就像经纪人以明星的身份处理活动并做额外的事情。
使用 Enhancer 类创建代理对象,就像让经纪人以明星的身份去安排活动。
通过代理对象调用方法,就像通过经纪人来安排明星的活动。
五、源码里面有哪些地方使用了代理?
1. Spring AOP
Spring AOP(面向切面编程)就像是一个超级经纪人团队,它会根据不同的明星(目标对象)和活动(方法),自动安排合适的经纪人(代理)来处理活动前后的额外事情,比如记录日志、管理事务等。Spring AOP 会根据目标对象是否实现接口来选择使用 JDK 动态代理或 CGLIB 动态代理。
2. MyBatis
MyBatis 在创建 Mapper 接口的实现时,就像是给每个明星(Mapper 接口)都安排了一个专属的经纪人(代理对象)。这个经纪人会把 SQL 语句的执行和结果映射封装起来,让开发者就像直接和明星交流一样,调用 Mapper 接口的方法。
六、代理底层原理
1. JDK 动态代理底层原理
JDK 动态代理的核心是 Proxy 类和 InvocationHandler 接口。Proxy 类就像是一个魔法工厂,它可以在运行的时候动态地生成代理类的字节码,然后把这些字节码加载到 Java 虚拟机(JVM)里。代理类会继承 java.lang.reflect.Proxy 类,并且实现我们指定的接口。当我们调用代理对象的方法时,实际上是调用了 InvocationHandler 的 invoke 方法,在这个方法里我们可以添加额外的逻辑,然后再调用真实对象的方法。
2. CGLIB 动态代理底层原理
CGLIB 动态代理的核心是 Enhancer 类和 MethodInterceptor 接口。Enhancer 类就像是一个克隆工厂,它会通过继承被代理的类,在运行时动态地生成代理类的字节码,然后加载到 JVM 里。当我们调用代理对象的方法时,会触发 MethodInterceptor 的 intercept 方法,在这个方法里我们可以添加额外的逻辑,然后通过 MethodProxy 调用父类(也就是被代理类)的方法。
七、Spring Boot 项目中真实使用案例
1. 添加依赖
在 Spring Boot 项目里使用代理,我们需要添加 Spring AOP 的依赖。就像请一个专业的经纪人团队,需要先办理一些相关的手续。在 pom.xml 中添加以下依赖:
org.springframework.boot
spring-boot-starter-aop
2. 创建服务类
创建一个服务类,就像有一个明星要参加活动。
import org.springframework.stereotype.Service;
// 使用 @Service 注解将该类标记为服务类,由 Spring 容器进行管理
@Service
// 定义一个 UserService 类,用于处理用户相关的业务逻辑
public class UserService {
// 定义一个 addUser 方法,用于添加用户
// 参数 username 表示要添加的用户的名称
public void addUser(String username) {
// 打印信息,表示正在添加用户
System.out.println("Adding user: " + username);
}
}
3. 创建切面类
创建一个切面类,就像给明星安排一个经纪人,在活动前后做一些额外的事情。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
// 使用 @Aspect 注解将该类标记为切面类,切面类是 AOP 的核心概念,用于定义通知和切入点
// 使用 @Component 注解将该类纳入 Spring 容器的管理,这样 Spring 才能识别并使用这个切面类
@Aspect
@Component
public class UserServiceAspect {
// @Before 注解表示这是一个前置通知,会在目标方法执行之前执行
// "execution(* com.example.demo.service.UserService.addUser(..))" 是一个切入点表达式
// execution 表示匹配方法执行连接点
// * 表示返回值类型任意
// com.example.demo.service.UserService.addUser 明确了要匹配的方法是 UserService 类中的 addUser 方法
// (..) 表示方法的参数列表任意
@Before("execution(* com.example.demo.service.UserService.addUser(..))")
// JoinPoint 表示连接点,通过它可以获取目标方法的相关信息,如方法名、参数等
public void beforeAddUser(JoinPoint joinPoint) {
// 通过 joinPoint.getArgs() 可以获取目标方法的参数数组
// 这里假设 addUser 方法只有一个参数,所以取 args[0] 就是用户名
System.out.println("Before adding user: " + joinPoint.getArgs()[0]);
}
// @After 注解表示这是一个后置通知,会在目标方法执行之后执行,无论目标方法是否抛出异常
// 同样使用了切入点表达式来指定要匹配的目标方法
@After("execution(* com.example.demo.service.UserService.addUser(..))")
public void afterAddUser(JoinPoint joinPoint) {
// 打印在添加用户之后的信息,包含传入的用户名
System.out.println("After adding user: " + joinPoint.getArgs()[0]);
}
}
4. 创建控制器类
创建一个控制器类,就像安排活动的组织者,负责接收客户端的请求并调用相应的服务。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
// @RestController 注解表示这是一个 RESTful 风格的控制器,会将方法的返回值自动转换为 JSON 格式响应给客户端
@RestController
public class UserController {
// @Autowired 注解用于自动注入 UserService 实例,Spring 会在容器中查找 UserService 类型的 bean 并注入进来
@Autowired
private UserService userService;
// @GetMapping 注解表示该方法处理 HTTP GET 请求
// "/addUser/{username}" 是请求的路径,{username} 是路径变量,用于接收客户端传入的用户名
@GetMapping("/addUser/{username}")
// @PathVariable 注解用于将路径变量绑定到方法的参数上
public String addUser(@PathVariable String username) {
// 调用 UserService 的 addUser 方法添加用户
userService.addUser(username);
// 返回一个表示用户添加成功的消息
return "User added successfully.";
}
}
5. 测试
启动 Spring Boot 应用程序,访问
http://localhost:8080/addUser/John,在控制台可以看到切面类中添加的日志输出。这就好比明星参加活动时,经纪人在活动前后做的额外事情被记录下来了。当客户端发起这个请求时,UserController 会接收到请求,调用 UserService 的 addUser 方法。由于我们定义了 UserServiceAspect 切面,在 addUser 方法执行前后,会分别触发 beforeAddUser 和 afterAddUser 方法,从而在控制台输出相应的日志信息。通过这种方式,我们利用代理(AOP 底层基于代理实现)实现了在不修改 UserService 核心业务逻辑的情况下,添加了额外的日志记录功能。
通过以上内容,相信你对 Java 代理已经有了全面的了解。无论是生活中的明星和经纪人,还是软件开发中的代理模式,它们的本质都是为了让事情更加高效、有序地进行。希望这篇文章能帮助你彻底搞懂 Java 代理。
代理和 AOP 的关系
代理:代理是一种设计模式,其核心思想是通过代理对象来控制对真实对象的访问。代理对象和真实对象实现相同的接口(JDK 动态代理)或者继承相同的类(CGLIB 动态代理),在代理对象中可以添加额外的逻辑来增强或控制对真实对象方法的调用。例如,在前面提到的静态代理和动态代理示例中,代理对象在调用真实对象方法的前后添加了日志记录等操作。
AOP(面向切面编程):AOP 是一种编程范式,它的主要目的是将横切关注点(如日志记录、事务管理、权限验证等)从业务逻辑中分离出来,以提高代码的可维护性和可复用性。AOP 通常基于代理模式来实现,通过在程序运行时动态地将切面逻辑(额外的处理逻辑)织入到目标对象的方法调用中。
所以,代理是实现 AOP 的一种手段,但代理本身是一个更广泛的概念,不仅仅局限于 AOP。
代理可以单独处理的事情
代理可以独立完成很多功能,并不依赖于 AOP 框架,以下是一些例子:
远程代理:在分布式系统中,当需要调用远程服务时,可以使用代理对象作为远程服务的本地代表。客户端只需要与代理对象进行交互,代理对象负责处理网络通信的细节,隐藏了远程调用的复杂性。例如,Java 的 RMI(远程方法调用)就使用了代理机制。
虚拟代理:在创建开销较大的对象时,可以使用虚拟代理来延迟对象的创建。只有在真正需要使用该对象时,代理对象才会创建真实对象。比如,在加载大图片时,可以先使用一个虚拟代理对象显示一个占位符,等图片真正加载完成后再替换为真实的图片对象。
保护代理:用于控制对真实对象的访问权限。代理对象在调用真实对象的方法之前,会先进行权限检查,只有具有相应权限的用户才能访问真实对象的方法。
除 Spring AOP 外使用代理的场景
MyBatis:MyBatis 在创建 Mapper 接口的实现时使用了 JDK 动态代理。Mapper 接口通常只定义了方法签名,没有具体的实现代码。MyBatis 通过动态代理为 Mapper 接口生成代理对象,在代理对象中实现了 SQL 语句的执行和结果映射,使得开发者可以像调用普通方法一样调用 Mapper 接口的方法。
Hibernate:Hibernate 是一个流行的 Java 持久化框架,它在实现懒加载(Lazy Loading)功能时使用了代理。当查询一个对象时,Hibernate 可能不会立即加载该对象的所有关联对象,而是返回一个代理对象。只有在真正访问关联对象的属性时,代理对象才会触发实际的数据库查询操作。
RPC 框架(如 Dubbo):Dubbo 是一个高性能的 Java RPC 框架,它在服务调用过程中使用了代理机制。服务消费者通过代理对象来调用远程服务提供者的方法,代理对象负责处理网络通信、序列化和反序列化等操作,使得服务调用就像调用本地方法一样简单。
综上所述,代理是一种非常强大且灵活的设计模式,它可以独立完成很多任务,并且在众多框架和系统中都有广泛的应用,不仅仅局限于 Spring AOP 框架。
代理与代理模式
代理本质上就是运用代理模式来达成特定目的。代理模式是一种结构型设计模式,它允许通过代理对象来控制对另一个对象(真实对象)的访问,代理对象在客户端和真实对象之间起到中介的作用。
Spring Boot 框架项目中代理模式的业务场景及实战例子
1. 日志记录
业务场景:在系统运行过程中,记录方法的调用信息,如方法名、参数、调用时间、执行结果等,方便后续的调试、监控和审计。
实战例子:在一个电商系统中,对于用户下单的操作,需要记录详细的日志信息。可以通过代理模式在下单方法调用前后添加日志记录逻辑。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class OrderLogAspect {
@Before("execution(* com.example.ecommerce.service.OrderService.placeOrder(..))")
public void beforePlaceOrder(JoinPoint joinPoint) {
System.out.println("Before placing order. Method: " + joinPoint.getSignature().getName() +
", Parameters: " + Arrays.toString(joinPoint.getArgs()));
}
@After("execution(* com.example.ecommerce.service.OrderService.placeOrder(..))")
public void afterPlaceOrder(JoinPoint joinPoint) {
System.out.println("After placing order. Method: " + joinPoint.getSignature().getName());
}
}
2. 事务管理
业务场景:确保数据库操作的原子性、一致性、隔离性和持久性。在业务方法执行前后自动开启和提交事务,或者在出现异常时回滚事务。
实战例子:在一个银行系统中,进行转账操作时,需要保证转账的两个操作(转出和转入)要么都成功,要么都失败。可以使用 Spring 的声明式事务管理,其底层基于代理模式实现。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransferService {
@Transactional
public void transferMoney(long fromAccountId, long toAccountId, double amount) {
// 转出操作
// 转入操作
}
}
3. 权限验证
业务场景:在调用某些敏感方法之前,检查用户是否具有相应的权限。只有具备权限的用户才能执行该方法,否则抛出权限不足的异常。
实战例子:在一个内容管理系统中,只有管理员用户才能删除文章。可以通过代理模式在删除文章的方法调用前进行权限验证。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ArticlePermissionAspect {
@Before("execution(* com.example.cms.service.ArticleService.deleteArticle(..))")
public void beforeDeleteArticle(JoinPoint joinPoint) {
// 模拟权限验证
boolean isAdmin = checkUserIsAdmin();
if (!isAdmin) {
throw new SecurityException("You do not have permission to delete articles.");
}
}
private boolean checkUserIsAdmin() {
// 实际项目中需要从用户会话或其他安全机制中获取用户角色信息
return false;
}
}
4. 缓存处理
业务场景:为了提高系统性能,减少数据库查询次数,在方法调用前先检查缓存中是否存在所需的数据。如果存在,则直接从缓存中获取;如果不存在,则调用实际方法并将结果存入缓存。
实战例子:在一个新闻网站中,经常需要获取热门新闻列表。可以使用代理模式在获取热门新闻的方法调用前检查缓存。
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.List;
@Aspect
@Component
public class NewsCacheAspect {
private Cache cache = CacheManager.getInstance().getCache("newsCache");
@Around("execution(* com.example.news.service.NewsService.getHotNews(..))")
public Object aroundGetHotNews(ProceedingJoinPoint joinPoint) throws Throwable {
String cacheKey = "hotNews";
Element element = cache.get(cacheKey);
if (element != null) {
return element.getObjectValue();
}
Object result = joinPoint.proceed();
cache.put(new Element(cacheKey, result));
return result;
}
}
5. 远程服务调用
业务场景:在分布式系统中,调用远程服务时,通过代理对象隐藏网络通信的细节,使得调用远程服务就像调用本地方法一样简单。
实战例子:在一个微服务架构的系统中,订单服务需要调用库存服务的接口来检查商品库存。可以使用 Feign 客户端,它基于代理模式实现了远程服务的调用。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "inventory-service")
public interface InventoryServiceClient {
@GetMapping("/inventory/{productId}")
int getInventory(@PathVariable("productId") String productId);
}
在订单服务中,可以直接注入 InventoryServiceClient 并调用其方法,Feign 会自动处理远程调用的细节。
Java 开发工程师使用代理的场景及案例
1. 常见用途
日志记录与调试:在开发过程中,为了方便定位问题和监控系统运行状态,需要记录方法的调用信息。使用代理可以在不修改原有业务逻辑的前提下,添加日志记录功能。
权限验证:确保只有具有相应权限的用户才能访问某些敏感方法或资源,增强系统的安全性。
事务管理:保证数据库操作的原子性、一致性、隔离性和持久性,避免数据不一致的问题。
2. 项目实战案例 - 日志记录
假设我们有一个简单的用户服务类,用于处理用户相关的业务逻辑。开发工程师可以使用 JDK 动态代理为该服务类添加日志记录功能。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 用户服务接口
interface UserService {
void createUser(String username);
}
// 用户服务实现类
class UserServiceImpl implements UserService {
@Override
public void createUser(String username) {
System.out.println("Creating user: " + username);
}
}
// 日志记录调用处理器
class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method call: " + method.getName());
return result;
}
}
// 测试类
public class DeveloperProxyExample {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
LoggingInvocationHandler handler = new LoggingInvocationHandler(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class>[]{UserService.class},
handler
);
proxy.createUser("JohnDoe");
}
}
在这个案例中,开发工程师通过 JDK 动态代理为 UserService 接口的实现类添加了日志记录功能,在方法调用前后输出相应的日志信息。
Java 架构师使用代理的场景及案例
1. 常见用途
系统架构设计与优化:通过代理模式实现系统的模块化和解耦,提高系统的可维护性和可扩展性。例如,使用代理来封装复杂的业务逻辑或外部服务调用,使得系统的各个模块之间的依赖关系更加清晰。
分布式系统通信:在分布式系统中,使用代理来处理远程服务调用,隐藏网络通信的细节,提高系统的性能和稳定性。
AOP 实现与切面管理:架构师负责设计和实现系统的切面逻辑,如事务管理、权限控制、缓存处理等,通过代理模式将这些横切关注点与业务逻辑分离。
- 项目实战案例 - 分布式系统中的远程服务调用
在一个微服务架构的电商系统中,订单服务需要调用库存服务来检查商品的库存情况。架构师可以使用 Feign(基于代理模式实现的声明式 HTTP 客户端)来实现远程服务调用。
首先,添加 Feign 依赖:
org.springframework.cloud
spring-cloud-starter-openfeign
定义库存服务接口:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "inventory-service")
public interface InventoryServiceClient {
@GetMapping("/inventory/{productId}")
int getInventory(@PathVariable("productId") String productId);
}
在订单服务中使用该接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private InventoryServiceClient inventoryServiceClient;
@GetMapping("/orders/{productId}")
public String createOrder(@PathVariable String productId) {
int inventory = inventoryServiceClient.getInventory(productId);
if (inventory > 0) {
// 处理订单创建逻辑
return "Order created successfully.";
} else {
return "Insufficient inventory.";
}
}
}
在这个案例中,架构师使用 Feign 代理实现了订单服务对库存服务的远程调用,隐藏了网络通信的细节,使得开发人员可以像调用本地方法一样调用远程服务。同时,架构师还可以通过 Feign 的配置来优化系统的性能和稳定性,如设置超时时间、重试机制等。