每天多学一点点~
话不多说,这就开始吧…
写代码至今也有三年了,一直有个梦想,能自己写一套中间件,与spring,springboot集成。正好这次公司有个项目需要集成阿里云的iot,虽然上云很简单,但是想着如何自己做成封装成一个jar,然后在业务工程中直接引用,下次有相同的项目来了,只需要引用jar,在yml中配置阿里云的参数即可,做到高扩展。
遂模仿springboot stater形式,参考dubbo的 Invocation理念,用动态代理的形式(其实aop就足矣),注入到spring的生命周期中。此文记录一下springboot的思路(spring其实一样,只是少了@ConditionalOnClass,@ConditionalOnProperty等动态注解)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version><relativePath/></parent><groupId>com.zjq.iot.springboot</groupId><artifactId>springboot-iot-starter</artifactId><version>MR.TieXinQiao</version><name>springboot-iot-starter</name><description>SpringBoot自定义starter</description><packaging>pom</packaging><properties><java.version>1.8</java.version></properties><modules><module>iot-spring-boot-starter</module><module>iot-spring-boot-starter-autoconfigurer</module></modules><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency></dependencies><build><plugins><!--提供source--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-source-plugin</artifactId><configuration><attach>true</attach></configuration><executions><execution><phase>compile</phase><goals><goal>jar</goal></goals></execution></executions></plugin></plugins></build></project>
这里面 只有一个 spring-boot-starter 的 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0"xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.zjq.iot.springboot</groupId><artifactId>springboot-iot-starter</artifactId><version>MR.TieXinQiao</version></parent><groupId>com.zjq.iot.springboot</groupId><artifactId>iot-spring-boot-starter</artifactId><description>启动器(starter)是一个空的jar文件,仅仅提供辅助性依赖管理,这些依赖需要自动装配或其他类库。</description><dependencies><!--引入 自己写的 iot autoconfigure--><dependency><groupId>com.zjq.iot.springboot</groupId><artifactId>iot-spring-boot-starter-autoconfigurer</artifactId><version>MR.TieXinQiao</version></dependency><!--如果当前starter 还需要其他的类库就在这里引用--></dependencies>
</project>
这是一个空的springboot工程,仅仅提供辅助性依赖管理,这些依赖需要自动装配或其他类库。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0"xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><artifactId>springboot-iot-starter</artifactId><groupId>com.zjq.iot.springboot</groupId><version>MR.TieXinQiao</version></parent><groupId>com.zjq.iot.springboot</groupId><artifactId>iot-spring-boot-starter-autoconfigurer</artifactId><description>iot自动装配类</description><dependencies><!--web 类--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--‐导入配置文件处理器,配置文件进行绑定就会有提示--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!--阿里云Java SDK公共包Maven依赖坐标 --><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.6</version><!--optional true 依赖不会 传递 一般配合 @ConditionalOnClass(XXX.class) 使用 --><optional>false</optional></dependency><!--阿里云 物联网 AMQP--><!-- amqp 1.0 qpid client --><dependency><groupId>org.apache.qpid</groupId><artifactId>qpid-jms-client</artifactId><version>0.47.0</version></dependency><!-- util for base64--><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.10</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional><version>1.18.8</version></dependency><dependency><groupId>org.apachemons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency></dependencies></project>
工程目录结构
├─ main
│ ├─ java
│ │ └─ com
│ │ └─ zjq
│ │ └─ iot
│ │ ├─ anno
│ │ │ ├─ EnableIotEngine.java
│ │ │ ├─ Invocation.java
│ │ │ └─ IotService.java
│ │ ├─ controller
│ │ │ └─ IndexController.java
│ │ ├─ DirectoryTreeV1.java
│ │ ├─ init
│ │ │ ├─ AmqpJavaClient.java
│ │ │ └─ IotInit.java
│ │ ├─ IotAutoConfitguration.java
│ │ ├─ properties
│ │ │ └─ IotProperties.java
│ │ ├─ proxy
│ │ │ └─ ProxyFactory.java
│ │ ├─ register
│ │ │ ├─ BeanCache.java
│ │ │ ├─ IotEngine.java
│ │ │ └─ IotScannerRegistrar.java
│ │ └─ utils
│ │ ├─ ClassUtil.java
│ │ └─ SpringBeanUtil.java
│ └─ resources
│ └─ META-INF
│ └─ spring.factories
└─ test└─ java
命名规范
官方命名空间
自定义命名空间
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zjq.iot.IotAutoConfitguration
IotAutoConfitguration
/* ━━━━━━佛祖保佑━━━━━━* ,;,,;* ,;;'( 社* __ ,;;' ' 会* /' ''~~'~' /'.) 主* ,;( ) / |. 义*,;' /-.,,( ) 码* ) / ) / )| 农* || || )* (_ (_* ━━━━━━永无BUG━━━━━━* @author :zjq* @date :2021/3/26 13:32* @description: TODO iot-springboot 自动装配* @version: V1.0* @slogan: 天下风云出我辈,一入代码岁月催*/
@Configuration
@ConditionalOnClass(IotProperties.class)
@ConditionalOnProperty(prefix = "aliyun", name = "isUse", havingValue = "true")
@EnableConfigurationProperties(IotProperties.class)
//@ConditionalOnMissingBean(IotAutoConfitguration.class) // 当给定的在 bean不存在时,则实例化当前Bean
public class IotAutoConfitguration {@Autowiredprivate IotProperties iotProperties;/*** 启动 加载器** @return*/@Beanpublic IotInit iotInit() {return new IotInit();}/*** 加载 页面** @return*/@Beanpublic IndexController indexController() {return new IndexController(iotProperties);}@Beanpublic SpringBeanUtil springBeanUtil() {return new SpringBeanUtil();}}
/* ━━━━━━佛祖保佑━━━━━━* ,;,,;* ,;;'( 社* __ ,;;' ' 会* /' ''~~'~' /'.) 主* ,;( ) / |. 义*,;' /-.,,( ) 码* ) / ) / )| 农* || || )* (_ (_* ━━━━━━永无BUG━━━━━━* @author :zjq* @date :2021/2/22 22:12* @description: TODO 自定义扫描注解* @version: V1.0* @slogan: 天下风云出我辈,一入代码岁月催*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
@Import({IotScannerRegistrar.class}) // 必须在这里导入 IotScannerRegistrar
public @interface EnableIotEngine {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};// Class<? extends Annotation> annotationClass() default Annotation.class;
}
IotService
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface IotService {/*** 名称** @return*/String value() default "";/*** 增强的类** @return*/
// String refrence() default "com.huanbao.iot.service.iot.AbstractDefaultIotService";String refrence() default "";String methodName() default ""; // 因为iot参数已经限制死了,所以没必要
}
@Data
public class Invocation {/*** 代理工厂*/private ProxyFactory handler;/*** 代理类*/private Object proxy;/*** 方法*/private Method method;/*** 参数*/
// private Object[] args;/*** 关联逻辑类*/private String reference;/*** 方法名*/private String methodName;}
BeanCache类 (想到了spring解决循环依赖用了三级缓存,这里为了不每次从容器中获取bean,遂写了个单例的bean的缓存池)
/* ━━━━━━佛祖保佑━━━━━━* ,;,,;* ,;;'( 社* __ ,;;' ' 会* /' ''~~'~' /'.) 主* ,;( ) / |. 义*,;' /-.,,( ) 码* ) / ) / )| 农* || || )* (_ (_* ━━━━━━永无BUG━━━━━━* @author :zjq* @date :2021/3/5 16:35* @description: TODO bean 的缓存池* @version: V1.0* @slogan: 天下风云出我辈,一入代码岁月催*/
public class BeanCache {private volatile static BeanCache instance;/* 一级缓存 存 定义好的bean*/public volatile static Map<String, Invocation> singletonObjects;public static BeanCache getInstance() {if (instance == null) {synchronized (BeanCache.class) {if (instance == null) {instance = new BeanCache();}}}return instance;}/*** 获取值*/public Invocation getBean(String beanName) {if (null == singletonObjects) {throw new RuntimeException("iot bean is null");}Invocation invocation = (beanName);return invocation;}public Map<String, Invocation> getBeanMap() {if (null == singletonObjects) {throw new RuntimeException("iot bean is null");}return singletonObjects;}/*** 赋值*/public void putBean(String beanName, Invocation invocation) {if (null == singletonObjects) {singletonObjects = new ConcurrentHashMap<>();}singletonObjects.put(beanName, invocation);}}
IotScannerRegistrar类 ( 在业务工程springboot的启动类上会加上 @EnableIotEngine(value = “com.huanbao.iot”) ,所以此处就是拿到注解的包路径,扫描包,并且把加了@IotService(value = “myHeXuIotService”, refrence = “com.huanbao.iot.service.iot.CommonIotServiceImpl”, methodName = “doLogic”) 这样的注解的类注册到容器,然后走代理增强逻辑)
/* ━━━━━━佛祖保佑━━━━━━* ,;,,;* ,;;'( 社* __ ,;;' ' 会* /' ''~~'~' /'.) 主* ,;( ) / |. 义*,;' /-.,,( ) 码* ) / ) / )| 农* || || )* (_ (_* ━━━━━━永无BUG━━━━━━* @author :zjq* @date :2021/3/5 14:44* @description: TODO 注册 自定义 注解 bean* @version: V1.0* @slogan: 天下风云出我辈,一入代码岁月催*/
@Slf4j
public class IotScannerRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {/*** 从我们传入的配置类中来解析@EnableIotEngine 注解信息,然后吧 EnableIotEngine 注解的属性转化为 AnnotationAttributes类型(Map类型)*/AnnotationAttributes iotScanAttrs = AnnotationAttributes.AnnotationAttributes(Name()));if (iotScanAttrs != null) {/*** 调用重写的方法registerBeanDefinitions generateBaseBeanName(importingClassMetadata, 0) 我们即将注册的bean定义的名称* com.huanbao.iot.IotServerApplication#IotScannerRegistrar#0**/registerBeanDefinitions(iotScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));}}void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {/*** 解析@EnableIotEngine 扫描的的包或者是 class 对象*/String[] basePackageArr = StringArray("value");if (!(basePackageArr.length > 0)) {throw new RuntimeException("iot scan cannot be null");}for (int i = 0; i < basePackageArr.length; i++) {List<Class<?>> clsList = AllClassByPackageName(basePackageArr[i]);for (Class n : clsList) {IotService annotation = (IotService) n.getAnnotation(IotService.class);if (annotation != null) {// 注册成 bean定义RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(n);if (StringUtils.isNotBlank(annotation.value())) {isterBeanDefinition(annotation.value(), rootBeanDefinition);} else {Name()), rootBeanDefinition);}String refrence = frence();String referenceMethodName = hodName();if (StringUtils.isBlank(refrence)) {("############ " + n.getName() + "iot bean has not assign refrence");break;}if (StringUtils.isBlank(referenceMethodName)) {("############ " + n.getName() + "iot bean has not assign referenceMethodName");break;}try {// 获取接口 这里先只能获取jdk proxy ? why, 因为 cglib 我不会,后续 有空加入Class<?>[] interfaces = n.newInstance().getClass().getInterfaces();// 拿到代理 走增强ProxyFactory handler = new wInstance(), refrence, referenceMethodName);Object proxy = Proxy(n, interfaces);// 拿到接口方法Method method = Class().getInterfaces()[0].getMethod("doIotService", String.class, String.class, String.class);method.setAccessible(true);Invocation invocation = new Invocation();invocation.setHandler(handler);invocation.setProxy(proxy);invocation.setMethod(method);invocation.frence());invocation.setMethodName(referenceMethodName);if (StringUtils.isNotBlank(annotation.value())) {Instance().putBean(annotation.value(), invocation);} else {Instance().Name()), invocation);}log.info("############ " + n.getName() + " has already registed ");} catch (Exception e) {("iot bean has error");e.printStackTrace();}}}}log.info("########### all iot beans has already registed ##############");}/*** 方法实现说明:生成我们bean定义的名称** @param importingClassMetadata 传入的配置类* @param index 默认传入0* @author:xsls* @return: com.huanbao.iot.IotServerApplication#IotScannerRegistrar#0* @exception:* @date:2021/03/05 15:29*/private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {ClassName() + "#" + SimpleName() + "#" + index;}
}
/* ━━━━━━佛祖保佑━━━━━━* ,;,,;* ,;;'( 社* __ ,;;' ' 会* /' ''~~'~' /'.) 主* ,;( ) / |. 义*,;' /-.,,( ) 码* ) / ) / )| 农* || || )* (_ (_* ━━━━━━永无BUG━━━━━━* @author :zjq* @date :2021/3/26 9:32* @description: TODO spring 的 启动类 顺序在 扫包 之后* @version: V1.0* @slogan: 天下风云出我辈,一入代码岁月催*/
@Slf4j
public class IotInit implements CommandLineRunner {@Autowiredprivate IotProperties iotProperties;@Overridepublic void args) throws Exception {log.info("the iotProperties is :{} ", iotProperties);if (IsUse().equals("true")) {log.info("############ 项目启动,开启 iot 服务端 #################");String clientId = getLocalIp();AmqpJavaClient.AccessKeyId(), AccessSercret(),ConsumerGroupId(), IotInstanceId(), ConnectionUrl());log.info("############# iot 服务端 启动成功 ##################");}}public String getLocalIp() {InetAddress localHost = null;try {localHost = LocalHost();} catch (UnknownHostException e) {(e.getMessage(), e);}// 返回格式为String ip = HostAddress();return ip;}}
AmqpJavaClient类 阿里云的demo,这里只贴出关键部分代码,调用IotEngine.doInvoke(args);走代理增强
/*** 在这里处理您收到消息后的具体业务逻辑。*/private static void processMessage(Message message) {try {byte[] body = Body(byte[].class);String content = new String(body);String topic = StringProperty("topic");String messageId = StringProperty("messageId");LocalDateTime now = w();String time = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(now);log.info("**************************************************" + time);log.info("receive message"+ ", topic = " + topic+ ", messageId = " + messageId+ ", content = " + content);/*** TODO plan 0 自定义增强 自动 扫描*/Object[] args = new Object[]{content, topic, messageId};IotEngine.doInvoke(args);} catch (Exception e) {("processMessage occurs error ", e);}}
IotEngine类 从beanCache中取出需要调用的bean,从Invocation中获取各种参数,然后走代理
@Slf4j
public class IotEngine {public static volatile boolean isOpenIot = false;/*** 船新版本** @param args*/public static void doInvoke(Object[] args) {if (!isOpenIot) {log.warn("################## the iot server is closed ... so nothing is doinvoke .... ##############");return;}Map<String, Invocation> beanMap = Instance().getBeanMap();if (!(beanMap.size() > 0)) {log.warn("################### the server has not any iot beans #######################");return;}for (Map.Entry<String, Invocation> m : Set()) {Invocation v = m.getValue();ProxyFactory handler = v.getHandler();Method method = v.getMethod();Object proxy = v.getProxy();try {// 调用handler.invoke(proxy, method, args);} catch (Throwable throwable) {throwable.printStackTrace();}}}}
/* ━━━━━━佛祖保佑━━━━━━* ,;,,;* ,;;'( 社* __ ,;;' ' 会* /' ''~~'~' /'.) 主* ,;( ) / |. 义*,;' /-.,,( ) 码* ) / ) / )| 农* || || )* (_ (_* ━━━━━━永无BUG━━━━━━* @author :zjq* @date :2021/2/22 22:16* @description: TODO 代理类实现* @version: V1.0* @slogan: 天下风云出我辈,一入代码岁月催*/
@Slf4j
public class ProxyFactory<T> implements InvocationHandler {//目标类private Object target;// 增强类private String reference;// 增强类调用的方法private String referenceMethodName;public ProxyFactory(Object target, String reference, String referenceMethodName) {this.target = ference = ferenceMethodName = referenceMethodName;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {doReference(args);// issue 这里要不要调用 ? 调用,自动实现业务逻辑; 不调用,其实也可,但是参数已经限制了必须是iot那几个return method.invoke(this.target, args);}@SuppressWarnings("unchecked")public <T> T getProxy(Class clazz, Class<?>[] interfaceClass) {return (T) ClassLoader(), interfaceClass, this);}/*** 调用 增强类 即iot增强业务类** @param args*/public void doReference(Object[] args) {try {if (StringUtils.isBlank(reference)) {log.warn("############# reference class is not assign ##################");return;}if (StringUtils.isBlank(referenceMethodName)) {log.warn("############# methodName is not assign ##################");return;}Class<?> referenceClass = Class.forName(reference);// issue 参数这里拿的还是原方法的,要不要指定 reference 参数? 其实感觉没必要Method abstarctMethod = Method(referenceMethodName, String.class, String.class, String.class);
// Method[] declaredMethods = Interfaces()[0].getDeclaredMethods();
// Method abstarctMethod = declaredMethods[0];Service service = Annotation(Service.class);Object referenceBean;if (service != null) {referenceBean = Bean(referenceClass);} else {// 不一定非要实现 spring 自己的service ,如果是普通的类,直接 newInstance 就好referenceBean = wInstance();}abstarctMethod.invoke(referenceBean, args);} catch (Exception e) {log.warn("############# reference class ot found ##################");e.printStackTrace();}}@SuppressWarnings("unchecked")public static <T> T getProxy(final Class interfaceClass) {return (T) ClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}});}}
<!--自己写的 iot-springboot --><dependency><groupId>com.zjq.iot.springboot</groupId><artifactId>iot-spring-boot-starter</artifactId><version>MR.TieXinQiao</version></dependency>
在springboot启动类上加上 @EnableIotEngine(value = “com.huanbao.iot”)
在ymk文件中加上 阿里云iot参数
写一个 业务Service,加上 @IotService 注解 ,然后再指定的refrence 类上实现iot逻辑
@IotService(value = "myHeXuIotService", refrence = "com.huanbao.iot.service.iot.CommonIotServiceImpl", methodName = "doLogic")
public class HeXuBusinessServiceImpl implements LogicService {@Autowiredprivate RunDataMapper runDataMapper;@Overridepublic void doIotService(String content, String topic, String messageId) {System.out.println("实现 合续自己的 业务逻辑");}}
这个是 第一次 自己 根据 spring特性封装的一个自定义starter,里面用到了
但是,有时候真的不要过度封装,就比如这个业务,其实直接普通的调用方法也可以,真的没必要,只是有空,遂研究研究~
世上无难事,只
怕有心人,每天积累一点点,fighting!!!
2021,加油,fighting,希望可以少些crud啦!
本文发布于:2024-01-31 12:29:29,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170667536928519.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |