目录 一、前言 二、案例 1 代码 2 自定义代理类【静态代理】 2.1 一个接口多个实现,到底注入哪个依赖呢? 2.1.1 @Primary注解 2.1.2 @Resource注解(指定name属性) 2.1.3 @Qualifier注解 2.2 面向接口编程 2.3 如果没接口咋办呢? 3 动态代理
一、前言
在【对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 动态代理