重识Java动态代理(二)Spring中声明式编程实现

2023-11-19

一、声明式编程的好处

声明式编程的好处有:

  1. 代码简洁,不需要写很多相同的实现代码
  2. 对使用者屏蔽了实现细节,使用者只需要声明要做什么,而不关心怎么做。

二、适用场景

声明式编程适合封装公共的,不涉及业务逻辑的基础服务,例如远程调用,数据库访问。

三、Spring中声明式编程的实现

下面看一个在Spring中通过声明式编程实现远程访问的Demo,Demo类结构如下:

  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/


微信公众号:


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

重识Java动态代理(二)Spring中声明式编程实现 的相关文章

随机推荐

  • 进程间通信的方式总结(特点,以及code demo)

    进程间通信 IPC InterProcess Communication 是指在不同进程之间传播或交换信息 一 简单的进程间通信 命令行 父进程通过exec函数创建子进程时可以附加一些数据 环境变量 父进程通过exec函数创建子进程顺便传递
  • MOS管之增强型和耗尽型

    Depletion and enhancement modes In field effect transistors FETs depletion mode and enhancement mode are two major trans
  • 通过路由器端口映射实现外网IP访问内网服务器

    1 确认路由器的公网IP是不是真的公网IP 特别重要 如果不是可以不用看后面的了 通过www ip138 com网站可以查询当前网络的公网IP 再进入路由器控制界面查看wan口IP和公网IP是否相同 如果不同 大概率是私网IP 服务商在公网
  • cookie格式化

    字符串转成字典 使用场景 selenium尝试试用cookie登陆时 Network中cookie是一段字符串 需要转成字典使用 使用split和列表解析式 str thor 8954F43 Id d32def3ffSNw pn adsad
  • 数据结构---二叉查找树(二叉搜索树)

    二叉查找树 特性 插入 删除 待删除节点没有子节点 待删除节点有一个子节点 待删除节点有两个子节点 JAVA实现 缺陷 二叉查找树 二叉排序树 在二叉树的基础上 增加了 如果左子树不为空 则左子树上所有节点的值都小于根节点的值 如果右子树不
  • B站粉丝数显示器,播放数、获赞数失效解决。

    之前在B站看见很多人做B站粉丝数显示器 于是便在网上买了一块ESP8266的开发版回来尝试着折腾一个出来 便在网上搜索适合买回来的开发板和0 96 oled显示器的代码 显示部分的主要代码是在 果果小师弟的博客上找到的 然后自己又找了个可以
  • WORD中字号没有中文编号的解决办法

    今天一同事反映说 WORD中没有像 五号 四号 之类的字号 只有数字字号如 8 72 解决过程如下 一 删除office中normal dot模版文件 不行 二 开始 gt 程序 gt Microsoft Office 工具 gt Mics
  • 参数与超参数

    写在前面 关于训练深度学习模型最难的事情之一是你要处理的参数的数量 无论是从网络本身的层宽 宽度 层数 深度 连接方式 还是损失函数的超参数设计和调试 亦或者是学习率 批样本数量 优化器参数等等 这些大量的参数都会有网络模型最终的有效容限直
  • ftp的主动模式active mode和被动模式 passive mode的配置和区别

    共同点 客户端先发起命令连接 不同点 主动模式 服务端发起数据连接 客户端生成随机数据端口 被动模式 客户端发起数据连接 客户端和服务端都是随机数据端口 客户端与服务器的命令连接 服务器返回命令 PORT 2024 告诉客户端 服务器 用哪
  • SpringData使用ES报错 org.elasticsearch.index.mapper.MapperParsingException: No type specified for field

    原因就像mysql的字段要跟Java基本类型对应一样 ES的字段也要与Java的基本类型相对应 一 而且这个最好新建一个ES索引库 否则可能会有问题 我用Restful操作初始化了一个ES索引库 并增加数据 再用Java操作的时候 Spri
  • 父组件传来的值和子组件自己定义的data的值有什么区别?

    props和data的区别 1 data中的数据是组件内自己的数据 状态 可以随意修改data中的值 2 props的数据是父组件传递过来的数据 是只读的 只能供子组件使用 不能随意修改 下面进行演示 1 首先创建一个父组件HomeView
  • Windows Server 2019下搭建FTP服务器

    在服务器管理器中选择 添加角色和功能 连续点击下一步 跳过开始之前和安装类型界面 在服务器选择界面中 选择从服务器池中选择服务器 默认选中一台服务器 选中web服务复选框 点击下一步 选中FTP服务器复选框 点击安装 安装完毕后 可以在Wi
  • [YOLOv8/YOLOv7/YOLOv5系列算法改进NO.5]改进特征融合网络PANET为BIFPN(更新添加小目标检测层yaml)

    前 言 作为当前先进的深度学习目标检测算法YOLOv5 已经集合了大量的trick 但是在处理一些复杂背景问题的时候 还是容易出现错漏检的问题 此后的系列文章 将重点对YOLOv5的如何改进进行详细的介绍 目的是为了给那些搞科研的同学需要创
  • 从 微信 JS-SDK 认识 JSBridge

    前言 前段时间由于要实现 H5 移动端拉取微信卡包并同步卡包数据的功能 于是在项目中引入了 微信 JS SDK jweixin 1 相关包实现功能 但也由此让我对其产生了好奇心 于是打算好好了解下相关的内容 通过查阅相关资料发现这其实属于
  • [Linux-进程控制] 进程创建&进程终止&进程等待&进程程序替换&简易shell

    Linux 进程控制 进程创建 进程终止 进程等待 进程程序替换 简易shell 进程创建 fork函数回顾 双返回值 为什么要给子进程返回0 给父进程返回子进程的pid 如何理解fork会有两个返回值 调用fork之后 fork常规用法
  • Mac(M1)安装VMware虚拟机及Linux系统

    Mac M1 安装VMware虚拟机及Linux系统 网上大部分版本都是基于Intel芯片的 按照步骤安装后 M1芯片的Mac会报错 以下是M1芯片的Mac安装VMware虚拟机及Linux系统方法 1 安装VMware Fusion ht
  • python中的集合(Set)

    python中的集合 Set 在Python中 集合 Set 是一种无序 无重复元素的数据结构 集合通过花括号 或者使用 set 函数进行创建 与其他容器类型 如列表和字典 不同 集合中的元素是不可变的 不可被修改 且没有固定的顺序 特点
  • linux服务器上tomcat设置路径

    tomcat配置通过域名访问项目 是修改conf server xml里面的配置信息实现 具体如下 1 修改Connector节点的port属性值
  • 嵌入式Linux开发环境搭建

    嵌入式Linux开发环境搭建 工欲善其事 必先利其器 嵌入式Linux开发之路的开端 就是搭建开发环境 有了完善的开发环境 后面的学习之路就会方便很多 开发环境也是一个很浪费时间的过程 环境的搭建也非常多坑 希望大家能够快速搭建好环境 能够
  • 重识Java动态代理(二)Spring中声明式编程实现

    一 声明式编程的好处 声明式编程的好处有 代码简洁 不需要写很多相同的实现代码 对使用者屏蔽了实现细节 使用者只需要声明要做什么 而不关心怎么做 二 适用场景 声明式编程适合封装公共的 不涉及业务逻辑的基础服务 例如远程调用 数据库访问 三