天天看點

重識Java動态代理(二)Spring中聲明式程式設計實作

一、聲明式程式設計的好處

聲明式程式設計的好處有:

  1. 代碼簡潔,不需要寫很多相同的實作代碼
  2. 對使用者屏蔽了實作細節,使用者隻需要聲明要做什麼,而不關心怎麼做。

二、适用場景

聲明式程式設計适合封裝公共的,不涉及業務邏輯的基礎服務,例如遠端調用,資料庫通路。

三、Spring中聲明式程式設計的實作

下面看一個在Spring中通過聲明式程式設計實作遠端通路的Demo,Demo類結構如下:

重識Java動态代理(二)Spring中聲明式程式設計實作
  1. EnableRestClients是一個Spring的啟動類注解,用來聲明是否要開啟此功能,如果有此注解則開啟,否則不開啟。
  2. RestClient是要聲明的遠端接口的類注解,用來聲明這些接口是否要實作遠端調用。
  3. RestClientPath是要聲明的遠端接口的方法注解,用來聲明該方法要調用的遠端位址。
  4. RestClientsRegistrar是一個注冊類,作用是動态注冊遠端接口的實作類。
  5. RestClientFactoryBean是一個工廠Bean,作用是将遠端接口的實作作為一個Bean注冊到Spring中。
  6. RestClientProxyFactory是一個代理工廠,用于生成遠端接口的代理類。
  7. 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);
	}
           
  1. 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;
	}
}
           
  1. 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;
	}
}
           
  1. 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;
	}
}
           
  1. 接口類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/

微信公衆号:

重識Java動态代理(二)Spring中聲明式程式設計實作