对代理模式的理解

目录

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

相关推荐
肘击鸣的百k路12 小时前
Java 代理模式详解
java·开发语言·代理模式
OkeyProxy1 天前
怎麼解決IP地址衝突的問題?
代理模式·proxy模式·ip地址·代理服务器·海外ip代理
G皮T4 天前
【设计模式】结构型模式(二):代理模式
java·设计模式·编程·代理模式·proxy pattern·结构型模式
zzzhpzhpzzz5 天前
设计模式——代理模式
设计模式·系统安全·代理模式
羽愿5 天前
技术总结(十九)
代理模式
__Black_Opium__5 天前
设计模式 - 代理模式
设计模式·代理模式
OkeyProxy6 天前
路由器中怎麼設置代理IP?
代理模式·路由器·proxy模式·代理服务器·海外ip代理
decode127 天前
代码随想录算法训练营第三十二天 | 动态规划理论基础 509.斐波那契数 70.爬楼梯 746.使用最小花费爬楼梯
算法·动态规划·代理模式
收破烂的小熊猫~7 天前
动态规划(基础版)70.爬楼梯
算法·动态规划·代理模式
金池尽干7 天前
设计模式之——代理模式
c++·设计模式·代理模式