一、前言
在【对AOP的理解 】中,提到过代理模式。 本篇文章进一步谈谈我对代理模式的理解。
二、案例
1 代码
@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 自定义代理类【静态代理】
@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
@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注解
@Slf4j
@Service
@Primary
public class UserProxyServiceImpl implements UserService {
. . .
}
2.1.2 @Resource注解(指定name属性)
@RestController
@RequestMapping ( "/user" )
public class UserController {
@Resource ( name = "userProxyServiceImpl" )
private UserService userService;
. . .
}
IDEA的友好提示: 妈妈再也不担心我注不对bean了:)
2.1.3 @Qualifier注解
@Resource(name = “userProxyServiceImpl”)相当于:
@Autowired
@Qualifier ( "userProxyServiceImpl" )
@RestController
@RequestMapping ( "/user" )
public class UserController {
@Autowired
@Qualifier ( "userProxyServiceImpl" )
private UserService userService;
. . .
}
同样,IDEA提供了友好的提示:
2.2 面向接口编程
我们通过改变使用的bean:从UserServiceImpl换成了UserProxyServiceImpl,就新增了一些逻辑,例如,记录“登录”消耗的时间。 对调用者完全是无感的。
这就是通过接口来解耦了调用方和实现方:调用方–接口
–实现方。
2.3 如果没接口咋办呢?
2.3.1 示例
@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 继承
@RestController
@RequestMapping ( "/user" )
public class UserController {
@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 动态代理