对代理模式的理解

目录

  • 一、前言
  • 二、案例
    • [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 动态代理

相关推荐
敖行客 Allthinker9 小时前
VS Code 智能代理模式:重塑开发体验
vscode·代理模式
XU磊26019 小时前
代理模式(Proxy Pattern)详解:以延迟加载图片为例
java·代理模式
碎梦归途3 天前
23种设计模式-结构型模式之代理模式(Java版本)
java·开发语言·jvm·设计模式·代理模式
侧耳倾听1115 天前
java 设计模式之代理模式
java·设计模式·代理模式
〆、风神7 天前
Spring Boot实战:基于策略模式+代理模式手写幂等性注解组件
spring boot·代理模式·策略模式
忘梓.8 天前
解锁动态规划的奥秘:从零到精通的创新思维解析(9)
算法·动态规划·代理模式
_殊途8 天前
代理模式深度解析
代理模式
Antonio91511 天前
【设计模式】代理模式
设计模式·代理模式
NorthCastle13 天前
设计模式-结构型模式-代理模式
java·设计模式·代理模式
小马爱打代码13 天前
设计模式:代理模式 - 控制访问与增强功能的艺术
设计模式·代理模式