对代理模式的理解

目录

  • 一、前言
  • 二、案例
    • [1 代码](#1 代码)
    • [2 自定义代理类【静态代理】](#2 自定义代理类【静态代理】)
      • [2.1 一个接口多个实现,到底注入哪个依赖呢?](#2.1 一个接口多个实现,到底注入哪个依赖呢?)
        • [2.1.1 @Primary注解](#2.1.1 @Primary注解)
        • [2.1.2 @Resource注解(指定name属性)](#2.1.2 @Resource注解(指定name属性))
        • [2.1.3 @Qualifier注解](#2.1.3 @Qualifier注解)
      • [2.2 面向接口编程](#2.2 面向接口编程)
      • [2.3 如果没接口咋办呢?](#2.3 如果没接口咋办呢?)
        • [2.3.1 示例](#2.3.1 示例)
        • [2.3.2 继承](#2.3.2 继承)
    • [3 动态代理](#3 动态代理)

一、前言

  • 在【对AOP的理解】中,提到过代理模式。
  • 本篇文章进一步谈谈我对代理模式的理解。

二、案例

1 代码

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;

    @PostMapping("/login")
    public UserVO login(@RequestBody LoginRequest loginRequest) {
        UserDO userDO = userService.login(loginRequest.getUsername(), loginRequest.getPassword());
        return UserVO.builder()
                .username(userDO.getUsername())
                .password(userDO.getPassword())
                .build();
    }
}

public interface UserService {
    UserDO login(String username, String password);
}

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private LoginProcess loginProcess;

    @Override
    public UserDO login(String username, String password) {
        return loginProcess.login(username, password);
    }
}

@Component
public class LoginProcess {
    public UserDO login(String username, String password) {
		try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        
        return new UserDO()
                .setUsername("forrest")
                .setPassword("123456");
    }
}
  • 我们想知道"登录"过程耗费的时间,即loginProcess.login(username, password);耗费的时间。
  • 我们希望通过自定义代理类来实现。

2 自定义代理类【静态代理】

java 复制代码
@Slf4j
@Service
public class UserProxyServiceImpl implements UserService {
    @Resource
    private UserServiceImpl userServiceImpl;

    @Override
    public UserDO login(String username, String password) {
        long startTimestamp = System.currentTimeMillis();
        UserDO userDO = userServiceImpl.login(username, password);
        log.info("login cost {} ms", System.currentTimeMillis() - startTimestamp);
        return userDO;
    }
}
  • 如果这么写,很显然,启动时会报错:No qualifying bean of type 'structure.proxy.example3.service.UserService' available: expected single matching bean but found 2: userProxyServiceImpl,userServiceImpl
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
	
	...
}
  • UserService是接口,有两个实现类,Spring不知道到底要注入哪个bean,因此报错了。

2.1 一个接口多个实现,到底注入哪个依赖呢?

  • 在Spring框架中,当存在多个相同类型的bean时,可以通过三种主要方式来指定注入哪一个bean:使用@Primary注解@Resouce注解(指定name属性)@Qualifier注解
2.1.1 @Primary注解
java 复制代码
@Slf4j
@Service
@Primary
public class UserProxyServiceImpl implements UserService {
	...
}
2.1.2 @Resource注解(指定name属性)
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource(name = "userProxyServiceImpl")
    private UserService userService;
	
	...
}
  • IDEA的友好提示:
  • 妈妈再也不担心我注不对bean了:)
2.1.3 @Qualifier注解
  • @Resource(name = "userProxyServiceImpl")相当于:
java 复制代码
@Autowired
@Qualifier("userProxyServiceImpl")
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    @Qualifier("userProxyServiceImpl")
    private UserService userService;
	...
}
  • 同样,IDEA提供了友好的提示:

2.2 面向接口编程

  • 我们通过改变使用的bean:从UserServiceImpl换成了UserProxyServiceImpl,就新增了一些逻辑,例如,记录"登录"消耗的时间。
  • 对调用者完全是无感的。
    • 这就是通过接口来解耦了调用方和实现方:调用方--接口--实现方。

2.3 如果没接口咋办呢?

2.3.1 示例
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserServiceImpl userService;

    @PostMapping("/login")
    public UserVO login(@RequestBody LoginRequest loginRequest) {
        UserDO userDO = userService.login(loginRequest.getUsername(), loginRequest.getPassword());
        return UserVO.builder()
                .username(userDO.getUsername())
                .password(userDO.getPassword())
                .build();
    }
}

@Service
public class UserServiceImpl {
    @Resource
    private LoginProcess loginProcess;

    public UserDO login(String username, String password) {
        return loginProcess.login(username, password);
    }
}
2.3.2 继承
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
//    @Resource
//    private UserServiceImpl userService;

    @Resource
    private UserProxyServiceImpl userService;
	
	...
}

@Slf4j
@Service
public class UserProxyServiceImpl extends UserServiceImpl {
    @Resource
    private UserServiceImpl userServiceImpl;

    @Override
    public UserDO login(String username, String password) {
        long startTimestamp = System.currentTimeMillis();
        UserDO userDO = userServiceImpl.login(username, password);
        log.info("login cost {} ms", System.currentTimeMillis() - startTimestamp);
        return userDO;
    }
}
  • 很显然,所有用到UserServiceImpl的地方,都要换成UserProxyServiceImpl。麻烦啊!
  • 因此,如果依赖的实现方可能变化,一定要面向接口编程啊!
    • 如果第三方没提供接口,也要自定义一个接口来解耦调用方和实现方!

3 动态代理

相关推荐
panzer_maus2 天前
工厂模式、代理模式与单例模式的介绍
java·设计模式·代理模式
花月C3 天前
线性动态规划(Linear DP)
算法·动态规划·代理模式
散峰而望3 天前
【基础算法】动态规划从入门到进阶:记忆化搜索、线性 DP、LIS/LCS 一网打尽
c++·后端·算法·github·深度优先·动态规划·代理模式
淡忘旧梦3 天前
ChatGPT回答白屏
人工智能·chatgpt·代理模式
会编程的土豆4 天前
【数据结构与算法】动态规划
数据结构·c++·算法·leetcode·代理模式
范纹杉想快点毕业5 天前
C语言编程实战宝典:550例精解·涵盖基础语法·数组指针·位运算·递归·贪心·动态规划
c语言·动态规划·代理模式
想不明白的过度思考者5 天前
SpringAOP:Java 三大代理模式:从静态代理到 JDK & CGLIB 动态代理
java·开发语言·代理模式
※DX3906※7 天前
SpringBoot之旅5| 快速上手SpringAOP、深入刨析动态/静态两种代理模式
java·数据库·spring boot·后端·spring·java-ee·代理模式
无籽西瓜a7 天前
【西瓜带你学设计模式 | 第九期 - 代理模式】代理模式 —— 静态与动态代理实现、优缺点与适用场景
java·后端·设计模式·软件工程·代理模式
江畔何人初9 天前
iptables 和 IPVS 代理模式 Service 的区别
linux·运维·服务器·网络·云原生·kubernetes·代理模式