一、聲明式程式設計的好處
聲明式程式設計的好處有:
- 代碼簡潔,不需要寫很多相同的實作代碼
- 對使用者屏蔽了實作細節,使用者隻需要聲明要做什麼,而不關心怎麼做。
二、适用場景
聲明式程式設計适合封裝公共的,不涉及業務邏輯的基礎服務,例如遠端調用,資料庫通路。
三、Spring中聲明式程式設計的實作
下面看一個在Spring中通過聲明式程式設計實作遠端通路的Demo,Demo類結構如下:

- EnableRestClients是一個Spring的啟動類注解,用來聲明是否要開啟此功能,如果有此注解則開啟,否則不開啟。
- RestClient是要聲明的遠端接口的類注解,用來聲明這些接口是否要實作遠端調用。
- RestClientPath是要聲明的遠端接口的方法注解,用來聲明該方法要調用的遠端位址。
- RestClientsRegistrar是一個注冊類,作用是動态注冊遠端接口的實作類。
- RestClientFactoryBean是一個工廠Bean,作用是将遠端接口的實作作為一個Bean注冊到Spring中。
- RestClientProxyFactory是一個代理工廠,用于生成遠端接口的代理類。
- RestClientProxy是遠端接口的代理類,調用遠端接口方法時,實際調用的是代理類。
下面看下代碼:
1.EnableRestClients.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RestClientsRegistrar.class)
public @interface EnableRestClients {
/**
* 要掃描的接口類的包名
*
* @return
*/
String[] basePackages() default {};
}
2.RestClient.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestClient {
}
3.RestClientPath.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RestClientPath {
/**
* 出于示例簡單考慮,隻有一個遠端通路位址URL
*
* @return
*/
String url() default "";
}
4.RestClientsRegistrar核心代碼
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
System.out.println("this.resourceLoader: " + this.resourceLoader);
// 添加一個注解過濾器,有RestClient注解的類/接口才繼續處理
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(RestClient.class);
scanner.addIncludeFilter(annotationTypeFilter);
// 這裡的metadata是spring啟動類上的注解中繼資料,下面這一步是擷取EnableRestClients注解的屬性
Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableRestClients.class.getName());
// 得到EnableRestClients注解上的basePackages屬性值,隻掃描這些包下的class
Set<String> basePackages = new HashSet<>();
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String basePackage : basePackages) {
System.out.println("basePackage: " + basePackage);
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
// 掃描到的接口/類的注解中繼資料
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 得到RestClient注解的屬性,這裡不需要,因為在代理類中可以通過要代理的類的注解獲得
// Map<String, Object> attributes = annotationMetadata
// .getAnnotationAttributes(RestClient.class.getCanonicalName());
registerRestClient(registry, annotationMetadata);
}
}
}
}
/**
*
* @Description: 注冊Bean
* @param registry
* @param annotationMetadata
* @param attributes
*
* @Author 飛流
* @Date 2019年8月17日
*/
private void registerRestClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata) {
// 這個類就是掃描到的要處理的類
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(RestClientFactoryBean.class);
// 通過此方式給RestClientFactoryBean的成員指派,将要實作的類傳入
definition.addPropertyValue("type", className);
// 設定注入方式
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
// 通過RestClientFactoryBean生成指定類的實作,這個類就可以通過@Autowired注入了
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
- RestClientFactoryBean.java
class RestClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
private Class<?> type;
private ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() throws Exception {
}
@Override
public Object getObject() throws Exception {
return RestClientProxyFactory.getProxy(type);
}
@Override
public Class<?> getObjectType() {
return this.type;
}
@Override
public boolean isSingleton() {
return true;
}
}
- RestClientProxyFactory.java
public class RestClientProxyFactory {
public static Object getProxy(Class<?> clazz) {
RestClientProxy proxy = new RestClientProxy();
Object newInstanceObject = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, proxy);
return (Object) newInstanceObject;
}
}
- RestClientProxy.java
public class RestClientProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 擷取接口類方法上遠端位址
RestClientPath path = method.getAnnotation(RestClientPath.class);
String url = path.url();
System.out.println("path.url(): " + url);
RestTemplate restTemplate = new RestTemplate();
// 這裡arg[0]為方法參數
Object object = restTemplate.postForEntity(url, args[0], method.getReturnType()).getBody();
return object;
}
}
- 接口類UserClient.java
@RestClient
public interface UserClient {
@RestClientPath(url = "http://localhost:7000/findUserByName")
ResponseResult<User> findUserByName(String userName) throws Exception;
@RestClientPath(url = "http://localhost:7000/createUser")
ResponseResult<Void> createUser(User user) throws Exception;
}
可以看到這個接口類就是聲明式的,通過注解來指定要調用的服務端位址,方法參數以及傳回值和遠端服務的參數和傳回值對應,在實際調用時調用的是動态代理類實作的方法。
9. 接口類的使用
@RestController
public class CallUserController {
@Autowired
private UserClient client;
@PostMapping("/callFindUserByName")
public ResponseResult<User> callFindUserByName(@RequestBody String userName) throws Exception {
ResponseResult<User> response = client.findUserByName(userName);
return response;
}
@PostMapping("/callCreateUser")
public ResponseResult<Void> callCreateUser(@RequestBody User user) throws Exception {
return client.createUser(user);
}
}
可以看到這裡直接使用@Autowired注解來注入接口類,調用的也都是接口方法,而實際調用時會通過Java動态代理調用代理類的方法。
10. 模拟服務端
@RestController
public class UserController {
@PostMapping("/findUserByName")
public ResponseResult<User> findUserByName(@RequestBody String userName) throws Exception {
ResponseResult<User> response = new ResponseResult<User>();
User user = new User();
user.setUserName(userName);
user.setAge((int) (Math.random() * 50));
response.setResultObject(user);
return response;
}
@PostMapping("/createUser")
public ResponseResult<Void> createUser(@RequestBody User user) throws Exception {
ResponseResult<Void> response = new ResponseResult<Void>();
response.setResultMsg("Create user success.");
return response;
}
}
可以看到服務端參數和傳回值和用戶端接口類保持一緻。
至此就在Spring中實作了聲明式程式設計,完整執行個體代碼掃碼加入微信公衆号并回複:webfullstack,擷取倉庫位址。
end.
站點: http://javashizhan.com/
微信公衆号: