对代理模式的理解

目录

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

相关推荐
OkeyProxy3 小时前
什麼是ISP提供的公共IP地址?
代理模式·proxy模式·ip地址·isp·海外ip代理
kikyo哎哟喂13 小时前
Java 代理模式详解
java·开发语言·代理模式
hxj..1 天前
【设计模式】代理模式
java·设计模式·代理模式·动态代理
武子康1 天前
Java-05 深入浅出 MyBatis - 配置深入 动态 SQL 参数、循环、片段
java·sql·设计模式·架构·mybatis·代理模式
武子康2 天前
Java-04 深入浅出 MyBatis - SqlSessionFactory 与 SqlSession DAO与Mapper 代理模式
java·mysql·spring·mybatis·springboot·代理模式
南城花随雪。3 天前
Mybatis框架之代理模式 (Proxy Pattern)
mybatis·代理模式
JhonKI6 天前
【算法】动态规划中01背包问题解析
算法·动态规划·代理模式
OkeyProxy7 天前
如何恢復電腦IP地址的手動設置?
代理模式·ip·ip地址·代理服务器·海外ip代理
无限大.10 天前
动态规划与贪心算法:核心区别与实例分析
贪心算法·动态规划·代理模式
丶Darling.11 天前
Day42 | 动态规划 :选或不选 打家劫舍&&打家劫舍II
算法·动态规划·代理模式