百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Java 代理从 0 到彻底搞懂_java配置代理

wptr33 2025-02-21 15:38 14 浏览

一、为什么出现代理?

咱们先抛开编程,想象一下生活中的场景。假如你是一位大明星,每天都有无数的活动邀约、采访请求,还有各种商务合作的洽谈。要是你亲自去处理这些事情,那你哪还有时间去拍戏、唱歌、提升自己的业务能力呢?所以,你就会找一个经纪人。这个经纪人就相当于你的 “代理”。

经纪人会帮你筛选合适的活动,和合作方谈合同细节,安排你的行程,在你参加活动前后帮你处理各种杂事。这样一来,你就可以把精力都放在自己擅长的演艺事业上了。

在软件开发里,代理模式的出现也是出于类似的原因:

增强功能:就像经纪人会在你参加活动前帮你做好形象设计,活动后帮你做宣传推广一样。在不改变原本代码功能的基础上,给代码添加一些额外的功能,比如记录程序运行的日志、管理数据库事务、验证用户的权限等。
解耦职责:把一些通用的功能,比如记录日志、管理事务,从主要的业务逻辑中分离出来。就好比经纪人负责处理各种杂事,你专注于演艺事业,让代码的各个部分职责更清晰,也更容易维护和复用。
控制访问:经纪人会帮你筛选合作对象,只有符合你要求的合作才会让你参与。在程序里,代理可以控制对某个对象的访问,比如在调用方法之前检查用户有没有权限。
远程调用:假如你要和国外的一个剧组合作,你不可能每次都亲自飞去国外谈合作。这时候就可以通过你的经纪人在国外的代表和剧组沟通,这个代表就相当于远程代理。在分布式系统里,代理可以隐藏网络通信的复杂细节,让你调用远程的对象就像调用本地的对象一样方便。

二、什么是代理代码?

代理代码就是实现代理模式的具体代码。在 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 实现与切面管理:架构师负责设计和实现系统的切面逻辑,如事务管理、权限控制、缓存处理等,通过代理模式将这些横切关注点与业务逻辑分离。

  1. 项目实战案例 - 分布式系统中的远程服务调用
    在一个微服务架构的电商系统中,订单服务需要调用库存服务来检查商品的库存情况。架构师可以使用 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 的配置来优化系统的性能和稳定性,如设置超时时间、重试机制等。

相关推荐

Python自动化脚本应用与示例(python办公自动化脚本)

Python是编写自动化脚本的绝佳选择,因其语法简洁、库丰富且跨平台兼容性强。以下是Python自动化脚本的常见应用场景及示例,帮助你快速上手:一、常见自动化场景文件与目录操作...

Python文件操作常用库高级应用教程

本文是在前面《Python文件操作常用库使用教程》的基础上,进一步学习Python文件操作库的高级应用。一、高级文件系统监控1.1watchdog库-实时文件系统监控安装与基本使用:...

Python办公自动化系列篇之六:文件系统与操作系统任务

作为高效办公自动化领域的主流编程语言,Python凭借其优雅的语法结构、完善的技术生态及成熟的第三方工具库集合,已成为企业数字化转型过程中提升运营效率的理想选择。该语言在结构化数据处理、自动化文档生成...

14《Python 办公自动化教程》os 模块操作文件与文件夹

在日常工作中,我们经常会和文件、文件夹打交道,比如将服务器上指定目录下文件进行归档,或将爬虫爬取的数据根据时间创建对应的文件夹/文件,如果这些还依靠手动来进行操作,无疑是费时费力的,这时候Pyt...

python中os模块详解(python os.path模块)

os模块是Python标准库中的一个模块,它提供了与操作系统交互的方法。使用os模块可以方便地执行许多常见的系统任务,如文件和目录操作、进程管理、环境变量管理等。下面是os模块中一些常用的函数和方法:...

21-Python-文件操作(python文件的操作步骤)

在Python中,文件操作是非常重要的一部分,它允许我们读取、写入和修改文件。下面将详细讲解Python文件操作的各个方面,并给出相应的示例。1-打开文件...

轻松玩转Python文件操作:移动、删除

哈喽,大家好,我是木头左!Python文件操作基础在处理计算机文件时,经常需要执行如移动和删除等基本操作。Python提供了一些内置的库来帮助完成这些任务,其中最常用的就是os模块和shutil模块。...

Python 初学者练习:删除文件和文件夹

在本教程中,你将学习如何在Python中删除文件和文件夹。使用os.remove()函数删除文件...

引人遐想,用 Python 获取你想要的“某个人”摄像头照片

仅用来学习,希望给你们有提供到学习上的作用。1.安装库需要安装python3.5以上版本,在官网下载即可。然后安装库opencv-python,安装方式为打开终端输入命令行。...

Python如何使用临时文件和目录(python目录下文件)

在某些项目中,有时候会有大量的临时数据,比如各种日志,这时候我们要做数据分析,并把最后的结果储存起来,这些大量的临时数据如果常驻内存,将消耗大量内存资源,我们可以使用临时文件,存储这些临时数据。使用标...

Linux 下海量文件删除方法效率对比,最慢的竟然是 rm

Linux下海量文件删除方法效率对比,本次参赛选手一共6位,分别是:rm、find、findwithdelete、rsync、Python、Perl.首先建立50万个文件$testfor...

Python 开发工程师必会的 5 个系统命令操作库

当我们需要编写自动化脚本、部署工具、监控程序时,熟练操作系统命令几乎是必备技能。今天就来聊聊我在实际项目中高频使用的5个系统命令操作库,这些可都是能让你效率翻倍的"瑞士军刀"。一...

Python常用文件操作库使用详解(python文件操作选项)

Python生态系统提供了丰富的文件操作库,可以处理各种复杂的文件操作需求。本教程将介绍Python中最常用的文件操作库及其实际应用。一、标准库核心模块1.1os模块-操作系统接口主要功能...

11. 文件与IO操作(文件io和网络io)

本章深入探讨Go语言文件处理与IO操作的核心技术,结合高性能实践与安全规范,提供企业级解决方案。11.1文件读写11.1.1基础操作...

Python os模块的20个应用实例(python中 import os模块用法)

在Python中,...