对代理模式的理解

目录

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

相关推荐
Niuguangshuo13 小时前
Python设计模式:代理模式
开发语言·python·代理模式
找了一圈尾巴2 天前
设计模式(结构性)-代理模式
设计模式·代理模式
oracleworm2 天前
Qt 代理
qt·代理模式
士别三日&&当刮目相看3 天前
JAVA学习*简单的代理模式
java·学习·代理模式
高飞的Leo3 天前
代理模式介绍及典型案例
代理模式
木子庆五3 天前
Android设计模式之代理模式
android·设计模式·代理模式
努力学习的小廉6 天前
【C++】 —— 笔试刷题day_9
开发语言·c++·代理模式
码农幻想梦8 天前
第五章 动态规划
算法·动态规划·代理模式
狮子再回头10 天前
snmp/mib采用子代理模式,编码,部署
代理模式·snmp·mib·snmp子代理