• JAVA面向对象之封装&多态
    一、封装性.1.1封装概述1、是面向对象三大特征之一(封装,继承,多态)2、是面向对象编程语言对现实世界的模拟,现实世界里成员变量和对象都是一个整体,归属者是对象,外界需要通过对象的同意才能对其进行相应操作1.2封装原则1.2.1.封装提高了数据的安全性      将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问            成员变量private,提供对应的getXxx()/setXxx()方法比如:public class student{        private string name;        public string getName(){        }               pub1ic void setName(string name)i           this.name=name;       }      private int age;        public int getAge(){       }          public void setAge(int age){              this.age=age;       } }1.2.2把代码用方法进行封装(可维护性和条理性)      把代码按照功能模块抽取成一个个方法,然后在main方法里调用就可以了,这样做,我们main方法里的代码实现了哪些功能一眼就能看出来,都是一个个功能的调用,没有冗余的代码,比较直观,方便阅读和后期的维护1.2.3隐藏了实现,操作简单(复用性)      在其他也有用到这个功能的地方,可以不用在写,直接调用方法,不用过于关注过程,达到了复用的效果,让事情变得更简单二、多态.2.1多态的概念多态:同一个事物,表现出来的多种形态:多态需要满足的前提条件:1、要有继承或实现关系2、要有方法的重写3、要有父类接收子类对象比如:public class Car {      public void weight(){            system.out.print1n("普通车重量...");       } } class Big extends Car { //重写父类的方法         pub1ic void weight(){             system. out.println("大车又高又大,而且重量.....");         } } class Small extends Car{ //重写父类的方法          public void weight({                 system. out.println("小车又小又快,而且重量.....");           } } /** 1、要有继承或实现关系 2、要有方法的重写 3、要有父类接收子类对象 **/ public class BigDemo {       public static void main(string[] args) {           //多态的表达形式:向上造型           Car c = new Big();        } }2.2 多态的好处与弊端好处: 提高代码的扩展性; 定义一个方法的时候,以父类作为参数,在调用时,传入子类进行 具体的操作;弊端:不能访问子类特有的方法;(可以通过类型强转来实现)2.3 多态的转型1、向上转型:参考多态的优点扩展---设计中的一个基本准则:里氏代换原则      在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果 一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。      里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在 程 序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。2、向下转型         子类 子类名称 = (子类)父类名称;
  • SSH 组合框架模式小知识分享
    SSH=Struts+Spring+Hibernate      集成SSH框架的系统从职责上分为四层:表示层、业务逻辑层、数据持久层和域模块层,以帮助开发人员在短期内搭建结构清晰、可复用性好、维护方便的Web应用程序。其中使用Struts作为系统的整体基础架构,负责MVC的分离,在Struts框架的模型部分,控制业务跳转,利用Hibernate框架对持久层提供支持,Spring做管理,管理struts和hibernate。      Struts 是一个很好的MVC框架,主要技术是Servlet和Jsp。Struts的MVC设计模式可以使我们的逻辑变得很清晰,让我们写的程序层次分明。基于Struts开发可以简化开发难度,提高开发效率。  Spring 提供了管理业务对象的一致方法,并鼓励注入对接口编程而不是对类编程的良好习惯,使我们的产品在最大程度上解耦。Hibernate 是用来持久化数据的,提供了完全面向对象的数据库操作。Hibernate对JDBC进行了非常轻量级的封装,它使得与关系型数据库打交道变得非常轻松。在Struts+Spring+Hibernate系统中,对象之间的调用流程如下: Struts——>Spring——>Hibernate  JSP——>Action——>Service——>DAO——>Hibernate比如:1.Spring的配置文件bean.xml<?xml version="1.0" encoding="UTF-8"?><beans         xmlns="http://www.springframework.org/schema/beans"         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"         xmlns:tx="http://www.springframework.org/schema/tx">     <bean id="dataSource"           class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">         <property name="jdbcUrl"                   value="jdbc:mysql://localhost:3306/samblog?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=true">         </property>         <property name="user" value="root"></property>         <property name="password" value="123456"></property>         <property name="driverClass" value="org.gjt.mm.mysql.Driver"/>     </bean>     <bean id="sessionFactory"           class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">         <property name="dataSource">             <ref bean="dataSource"/>         </property>         <property name="hibernateProperties">             <value>                 hibernate.dialect=org.hibernate.dialect.MySQL5Dialect                 hibernate.hbm2ddl.auto=update                 hibernate.show_sql=false                 hibernate.format_sql=false            </value>         </property>         <property name="mappingResources">             <list>                 <value>site/sambloger/domain/Users.hbm.xml</value>                 <value>site/sambloger/domain/Blog.hbm.xml</value>                 <value>site/sambloger/domain/Category.hbm.xml</value>                 <value>site/sambloger/domain/Comment.hbm.xml</value>             </list>         </property>     </bean>     <bean id="transactionManager"           class="org.springframework.orm.hibernate5.HibernateTransactionManager">         <property name="sessionFactory" ref="sessionFactory"/>     </bean>     <tx:annotation-driven transaction-manager="transactionManager"/>     <!-- 配置Blog  spring进行管理  服务层直接调用实现与数据库的CRUD-->     <bean id="blogDao" class="site.sambloger.dao.impl.BlogDAOImpl">         <property name="sessionFactory" ref="sessionFactory"/>     </bean>     <bean id="blogService" class="site.sambloger.service.impl.BlogServiceImpl" scope="prototype">         <property name="blogDao" ref="blogDao"/>     </bean>     <bean id="blogAction" class="site.sambloger.action.BlogAction">         <property name="blogService" ref="blogService"/>         <property name="commentService" ref="commentService"/>     </bean>     <!-- 配置Comment -->     <bean id="commentDao" class="site.sambloger.dao.impl.CommentDAOImpl">         <property name="sessionFactory" ref="sessionFactory"/>     </bean>     <bean id="commentService" class="site.sambloger.service.impl.CommentServiceImpl" scope="prototype">         <property name="commentDao" ref="commentDao"/>     </bean>     <bean id="commentAction" class="site.sambloger.action.CommentAction">         <property name="commentService" ref="commentService"/>         <property name="blogService" ref="blogService"/>     </bean>     <!-- 配置Users -->     <bean id="usersDao" class="site.sambloger.dao.impl.UsersDAOImpl">         <property name="sessionFactory" ref="sessionFactory"></property>     </bean>     <bean id="usersService" class="site.sambloger.service.impl.UsersServiceImpl" scope="prototype">         <property name="usersDao" ref="usersDao"/>     </bean>     <bean id="usersAction" class="site.sambloger.action.UsersAction">         <property name="userService" ref="usersService"></property>     </bean></beans>2.Struts的配置文件 struts.xml<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd"><struts>     <package name="samblog" extends="struts-default" namespace="/">              <action name="init" class="blogAction" method="init">                     <result name="success">/bloglist.jsp</result>             </action>             <action name="getBlog" class="blogAction" method="getBlog">                     <result name="success">/displayBlog.jsp</result>             </action>              <action name="getAllNote" class="blogAction" method="getAllNote">                 <result name="success">/notelist.jsp</result>             </action>             <action name="addComment" class="commentAction" method="addComment">                 <result name="success"  type="redirect">/getBlog</result>             </action>     </package></struts>3.Hibernate其中的一个配置文件:<?xml version="1.0" encoding="utf-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><!--      Mapping file autogenerated by MyEclipse Persistence Tools--><hibernate-mapping>     <class name="site.sambloger.domain.Blog" table="blog">         <!--id标签表示映射到数据库中是作为主键 其他property表示普通键-->         <id name="id" type="java.lang.Integer">             <column name="id" />             <generator class="increment" />         </id><!--该标签加N方 会有一个字段叫category_id作为外键参照1(Category)的主键字段 并且用来存储这个主键的信息-->         <many-to-one name="category" class="site.sambloger.domain.Category"  lazy="false" cascade="all">             <column name="category_id" not-null="true" />         </many-to-one>         <property name="title" type="java.lang.String">             <column name="title" length="400" not-null="true" />         </property>         <property name="content" type="java.lang.String">             <column name="content" length="4000" not-null="true" />         </property>         <property name="createdTime" type="java.util.Date">             <column name="created_time" length="10" not-null="true" />         </property><!--在一对多的关联中,在一的一方(Blog)设置inverse=”true”让多的一方来维护关联关系更有助于优化,因为可以减少执行update语句-->         <set name="comments" inverse="true">             <key>                 <column name="blog_id" not-null="true" />             </key>             <one-to-many class="site.sambloger.domain.Comment" />         </set>     </class></hibernate-mapping>Spring框架的作用和好处:    Spring框架提供了一个容器,该容器可以管理应用程序的组件,还提供了IoC和AoP机制,实现组件之间解耦,提高程序结构的灵活性,增强系统的可维护和可扩展性。     在SSH整合开发中,利用Spring管理Service、DAO等组件,利用IoC机制实现Action和Service,Service和DAO之间低耦合调用。利用AoP机制实现事务管理、以及共通功能的切入等。     功能是整合,好处是解耦。Hibernate中操作并发处理(乐观锁和悲观锁)    Hibernate框架可以使用锁的机制来解决操作并发。    a.悲观锁         在数据查询出来时,就给数据加一个锁,锁定。这样其他用户再执行删、改操作时不允许。当占用着事务结束,锁会自动解除。          Hibernate采用的是数据库锁机制实现悲观锁控制。        缺点:将并发用户操作同步开,一个一个处理。当一个用户处理时间比较长时,效率会比较低。      b.乐观锁         允许同时更新提交,但是最快的会成功,慢的失败。         在记录中追加一个字段值,用该字段值当做版本。当最先提交者提交后,会自动将版本字段值提升,这样其他用户提交,会发现版本低于数据库记录目前版本,因此抛出异常提示失败。    特点:允许用户同时处理,但只能有一个成功,其他失败,以异常方式提示。SSH工作流程       a.启动服务器,加载工程以及web.xml.           (实例化Lisener,Filter等组件,将Spring容器和Struts2控制创建)       b.客户端发送请求,所有请求进入Struts2控制器。控制器根据请求类型不同,分别处理。           (action请求,*.action会进入struts.xml寻找<action>配置.            其他请求,*.jsp会直接调用请求资源,生成响应信息)       c.Struts2控制器根据<action>配置调用一个Action对象处理。         整合方法一:将Action交给Spring容器          (Action对象由struts2-spring-plugin.jar插件提供的                      StrutsSpringObjectFactory负责去Spring容器获取)         整合方法二:将Action置于Spring容器之外          (Action对象由struts2-spring-plugin.jar插件提供的                      StrutsSpringObjectFactory负责创建,然后到Spring容器中寻找与Action属性匹配的Bean对象,给Action对象注入。(默认采用名称匹配规则)       d.Struts2控制器执行defaultStack拦截器、Action对象、Result等组件处理.       e.执行Action的execute业务方法时,如果使用Service或DAO采用Spring的IoC机制调用。       f.执行Result生成响应信息,执行后续拦截器处理       g.将响应信息输出。
  • [技术干货] 如何动态更新SpringBoot中的yml文件
    前言在系统运行过程中,可能由于一些配置项的简单变动需要重新打包启停项目,这对于在运行中的项目会造成数据丢失,客户操作无响应等情况发生,针对这类情况对开发框架进行升级提供yml文件实时修改更新功能。项目依赖项目基于的是2.0.0.RELEASE版本,所以snakeyaml需要单独引入,高版本已包含在内。 codeduidaima.com<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.23</version></dependency>网上大多数方法是引入spring-cloud-context配置组件调用ContextRefresher的refresh方法达到同样的效果,考虑以下两点未使用:1.开发框架使用了logback日志,引入spring-cloud-context会造成日志配置读取错误。2.引入spring-cloud-context会同时引入spring-boot-starter-actuator组件,会开放一些健康检查路由及端口,需要对框架安全方面进行额外控制。YML文件内容获取读取resource文件下的文件需要使用ClassPathResource获取InputStream。 codeduidaima.compublic String getTotalYamlFileContent() throws Exception {// 堆代码 duidaima.comString fileName = "application.yml";return getYamlFileContent(fileName);}public String getYamlFileContent(String fileName) throws Exception {ClassPathResource classPathResource = new ClassPathResource(fileName);return onvertStreamToString(classPathResource.getInputStream());}public static String convertStreamToString(InputStream inputStream) throws Exception{return IOUtils.toString(inputStream, "utf-8");}YML文件内容更新我们获取到yml文件内容后可视化显示到前台进行展示修改,将修改后的内容通过yaml.load方法转换成Map结构,再使用yaml.dumpAsMap转换为流写入到文件。 codeduidaima.compublic void updateTotalYamlFileContent(String content) throws Exception {String fileName = "application.yml";updateYamlFileContent(fileName, content);}public void updateYamlFileContent(String fileName, String content) throws Exception {Yaml template = new Yaml();Map<String, Object> yamlMap = template.load(content);ClassPathResource classPathResource = new ClassPathResource(fileName);Yaml yaml = new Yaml();//字符输出FileWriter fileWriter = new FileWriter(classPathResource.getFile());//用yaml方法把map结构格式化为yaml文件结构fileWriter.write(yaml.dumpAsMap(yamlMap));//刷新fileWriter.flush();//关闭流fileWriter.close();}YML属性刷新yml属性在程序中读取使用一般有三种:1.使用Value注解 codeduidaima.com@Value("${system.systemName}")private String systemName;2.通过enviroment注入读取 codeduidaima.com@Autowiredprivate Environment environment;environment.getProperty("system.systemName")3.使用ConfigurationProperties注解读取 codeduidaima.com@Component@ConfigurationProperties(prefix = "system")public class SystemConfig {private String systemName;}Property刷新我们通过environment.getProperty方法读取的配置集合实际是存储在PropertySources中的,我们只需要把键值对全部取出存储在propertyMap中,将更新后的yml文件内容转换成相同格式的ymlMap,两个Map进行合并,调用PropertySources的replace方法进行整体替换即可。但是yaml.load后的ymlMap和PropertySources取出的propertyMap两者数据解构是不同的,需要进行手动转换。propertyMap集合就是单纯的key,value键值对,key是properties形式的名称,例如system.systemName=>xxxxx集团管理系统。ymlMap集合是key,LinkedHashMap的嵌套层次结构,例如system=>(systemName=>xxxxx集团管理系统)。转换方法如下: codeduidaima.compublic HashMap<String, Object> convertYmlMapToPropertyMap(Map<String, Object> yamlMap) {HashMap<String, Object> propertyMap = new HashMap<String, Object>();for (String key : yamlMap.keySet()) {String keyName = key;Object value = yamlMap.get(key);if (value != null && value.getClass() == LinkedHashMap.class) {convertYmlMapToPropertyMapSub(keyName, ((LinkedHashMap<String, Object>) value), propertyMap);} else {propertyMap.put(keyName, value);}}return propertyMap;}private void convertYmlMapToPropertyMapSub(String keyName, LinkedHashMap<String, Object> submMap, Map<String, Object> propertyMap) {for (String key : submMap.keySet()) {String newKey = keyName + "." + key;Object value = submMap.get(key);if (value != null && value.getClass() == LinkedHashMap.class) {convertYmlMapToPropertyMapSub(newKey, ((LinkedHashMap<String, Object>) value), propertyMap);} else {propertyMap.put(newKey, value);}}}刷新方法如下 codeduidaima.comString name = "applicationConfig: [classpath:/" + fileName + "]";MapPropertySource propertySource = (MapPropertySource) environment.getPropertySources().get(name);Map<String, Object> source = propertySource.getSource();Map<String, Object> map = new HashMap<>(source.size());map.putAll(source);Map<String, Object> propertyMap = convertYmlMapToPropertyMap(yamlMap);for (String key : propertyMap.keySet()) {Object value = propertyMap.get(key);map.put(key, value);}environment.getPropertySources().replace(name, new MapPropertySource(name, map));注解刷新不论是Value注解还是ConfigurationProperties注解,实际都是通过注入Bean对象的属性方法使用的,我们先自定注解RefreshValue来修饰属性所在Bean的class。通过实现InstantiationAwareBeanPostProcessorAdapter接口在系统启动时过滤筛选对应的Bean存储下来,在更新yml文件时通过spring的event通知更新对应bean的属性即可。注册事件使用EventListener注解: codeduidaima.com@EventListenerpublic void updateConfig(ConfigUpdateEvent configUpdateEvent) {if(mapper.containsKey(configUpdateEvent.key)){List<FieldPair> fieldPairList = mapper.get(configUpdateEvent.key);if(fieldPairList.size()>0){for (FieldPair fieldPair:fieldPairList) {fieldPair.updateValue(environment);}}}}通知触发事件使用ApplicationContext的publishEvent方法: codeduidaima.com@Autowiredprivate ApplicationContext applicationContext;for (String key : propertyMap.keySet()) {applicationContext.publishEvent(new YamlConfigRefreshPostProcessor.ConfigUpdateEvent(this, key));}YamlConfigRefreshPostProcessor的完整代码如下: codeduidaima.com@Componentpublic class YamlConfigRefreshPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements EnvironmentAware {private Map<String, List<FieldPair>> mapper = new HashMap<>();private Environment environment;@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {processMetaValue(bean);return super.postProcessAfterInstantiation(bean, beanName);}@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}private void processMetaValue(Object bean) {Class clz = bean.getClass();if (!clz.isAnnotationPresent(RefreshValue.class)) {return;}if (clz.isAnnotationPresent(ConfigurationProperties.class)) {//@ConfigurationProperties注解ConfigurationProperties config = (ConfigurationProperties) clz.getAnnotation(ConfigurationProperties.class);for (Field field : clz.getDeclaredFields()) {String key = config.prefix() + "." + field.getName();if(mapper.containsKey(key)){mapper.get(key).add(new FieldPair(bean, field, key));}else{List<FieldPair> fieldPairList = new ArrayList<>();fieldPairList.add(new FieldPair(bean, field, key));mapper.put(key, fieldPairList);}}} else {//@Valuez注解try {for (Field field : clz.getDeclaredFields()) {if (field.isAnnotationPresent(Value.class)) {Value val = field.getAnnotation(Value.class);String key = val.value().replace("${", "").replace("}", "");if(mapper.containsKey(key)){mapper.get(key).add(new FieldPair(bean, field, key));}else{List<FieldPair> fieldPairList = new ArrayList<>();fieldPairList.add(new FieldPair(bean, field, key));mapper.put(key, fieldPairList);}}}} catch (Exception e) {e.printStackTrace();System.exit(-1);}}}public static class FieldPair {private static PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}",":", true);private Object bean;private Field field;private String value;public FieldPair(Object bean, Field field, String value) {this.bean = bean;this.field = field;this.value = value;}public void updateValue(Environment environment) {boolean access = field.isAccessible();if (!access) {field.setAccessible(true);}try {if (field.getType() == String.class) {String updateVal = environment.getProperty(value);field.set(bean, updateVal);}else if (field.getType() == Integer.class) {Integer updateVal = environment.getProperty(value,Integer.class);field.set(bean, updateVal);}else if (field.getType() == int.class) {int updateVal = environment.getProperty(value,int.class);field.set(bean, updateVal);}else if (field.getType() == Boolean.class) {Boolean updateVal = environment.getProperty(value,Boolean.class);field.set(bean, updateVal);}else if (field.getType() == boolean.class) {boolean updateVal = environment.getProperty(value,boolean.class);field.set(bean, updateVal);}else {String updateVal = environment.getProperty(value);field.set(bean, JSONObject.parseObject(updateVal, field.getType()));}} catch (IllegalAccessException e) {e.printStackTrace();}field.setAccessible(access);}public Object getBean() {return bean;}public void setBean(Object bean) {this.bean = bean;}public Field getField() {return field;}public void setField(Field field) {this.field = field;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}public static class ConfigUpdateEvent extends ApplicationEvent {String key;public ConfigUpdateEvent(Object source, String key) {super(source);this.key = key;}}@EventListenerpublic void updateConfig(ConfigUpdateEvent configUpdateEvent) {if(mapper.containsKey(configUpdateEvent.key)){List<FieldPair> fieldPairList = mapper.get(configUpdateEvent.key);if(fieldPairList.size()>0){for (FieldPair fieldPair:fieldPairList) {fieldPair.updateValue(environment);}}}}}转载自cid:link_0Group/Topic/JAVA/18012
  • [技术干货] 如何使用Spring Validation实现数据校验功能
    背景Spring 框架,广泛应用于 JAVA 企业级开发中,包含了一套实用的字段校验机制: Spring Validation。这个机制融合了 JSR380 规范,即 Bean Validation 2.0。本文将介绍 Spring Validation 的使用方法,包括基础注解的应用以及进阶使用技巧。常用注解Bean Validation 2.0 注解校验空值@Null:验证对象是否为 null@NotNull:验证对象是否不为 null@NotEmpty:验证对象不为 null,且长度(数组、集合、字符串等)大于 0@NotBlank:验证字符串不为 null,且去除两端空白字符后长度大于 0校验大小@Size(min=, max=):验证对象(数组、集合、字符串等)长度是否在给定的范围之内@Min(value):验证数值(整数或浮点数)是否大于等于指定的最小值@Max(value):验证数值是否小于等于指定的最大值校验布尔值@AssertTrue:验证 Boolean 对象是否为 true@AssertFalse:验证 Boolean 对象是否为 false校验日期和时间@Past:验证 Date 和 Calendar 对象是否在当前时间之前@Future:验证 Date 和 Calendar 对象是否在当前时间之后@PastOrPresent:验证日期是否是过去或现在的时间@FutureOrPresent:验证日期是否是现在或将来的时间正则表达式@Pattern(regexp=, flags=):验证 String 对象是否符合正则表达式的规则Hibernate Validation 拓展@Length(min=, max=):验证字符串的大小是否在指定的范围内@Range(min=, max=):验证数值是否在合适的范围内@UniqueElements:校验集合中的值是否唯一,依赖于 equals 方法@ScriptAssert:利用脚本进行校验@Valid 和 @Validated这两个注解是校验的入口,作用相似但用法上存在差异。  codeduidaima.com@Validated// 用于类/接口/枚举,方法以及参数@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Validated {// 校验时启动的分组Class<?>[] value() default {};}@Valid// 用于方法,字段,构造函数,参数,以及泛型类型@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })@Retention(RUNTIME)@Documentedpublic @interface Valid {// 未提供其他属性}作用范围不同:@Validated 无法作用在于字段, @Valid 无法作用于类;注解中的属性不同:@Validated 中提供了指定校验分组的属性,而 @Valid 没有这个功能,因为 @Valid 不能进行分组校验。字段校验场景及使用示例常见的校验场景有三种: Controller 层的校验、编程式校验、 Dubbo 接口校验。Controller层 的校验使用方式当方法入参为 @RequestBody 注解的 JavaBean,可在入参前使用 @Validated 或 @Valid 注解开启校验。 codeduidaima.com@PostMapping("/save")public Response<Boolean> saveNotice(@Validated @RequestBody NoticeDTO noticeDTO) {// noticeDTO中各字段校验通过,才会执行后续业务逻辑return Response.ok(true);}当方法入参为 @PathVariable、 @RequestParam 注解的简单参数时,需要在 Controller 加上 @Validated 注解开启校验。 codeduidaima.com@RequestMapping("/notice")@RestController// 必须加上该注解@Validatedpublic class UserController {// 路径变量@GetMapping("{id}")public Reponse<NoticeDTO> detail(@PathVariable("id") @Min(1L) Long noticeId) {// 参数noticeId校验通过,执行后续业务逻辑return Reponse.ok();}// 请求参数@GetMapping("getByTitle")public Result getByTitle(@RequestParam("title") @Length(min = 1, max = 20) String title) {// 参数title校验通过,执行后续业务逻辑return Result.ok();}}原理Spring 框架中的 HandlerMethodArgumentResolver 策略接口,负责将方法参数解析为特定请求中的参数值。 codeduidaima.compublic interface HandlerMethodArgumentResolver {// 判断当前解析器是否支持给定的方法参数boolean supportsParameter(MethodParameter var1);// 堆代码 duidaima.com@Nullable// 实际解析参数的方法Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;}上述接口针对 @RequestBody 的实现类 RequestResponseBodyMethodProcessor 中,存在字段校验逻辑,调用 validateIfApplicable 方法校验参数。// RequestResponseBodyMethodProcessorpublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 前置处理// 校验逻辑if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);if (arg != null) {//调用校验函数this.validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());}}// 数据绑定逻辑}// 返回处理结果return this.adaptArgumentIfNecessary(arg, parameter);}validateIfApplicable 方法中,根据方法参数上的注解,决定是否进行字段校验:当存在 @Validated 或以 Valid 开头的注解时,进行校验。 codeduidaima.comprotected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {// 获取参数上的注解Annotation[] annotations = parameter.getParameterAnnotations();Annotation[] var4 = annotations;int var5 = annotations.length;// 遍历注解for(int var6 = 0; var6 < var5; ++var6) {Annotation ann = var4[var6];// 获取 @Validated 注解Validated validatedAnn = (Validated)AnnotationUtils.getAnnotation(ann, Validated.class);// 或者注解以 Valid 开头if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {// 开启校验Object hints = validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann);Object[] validationHints = hints instanceof Object[] ? (Object[])((Object[])hints) : new Object[]{hints};binder.validate(validationHints);break;}}}@PathVariable 和 @RequestParam 对应的实现类中,则没有相应字段校验逻辑,因此需要在 Controller 上使用 @Validated,开启字段校验。编程式校验1.配置 Validator codeduidaima.com@Configurationpublic class ValidatorConfiguration {@Beanpublic Validator validator() {ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()// 设置是否开启快速失败模式//.failFast(true).buildValidatorFactory();return validatorFactory.getValidator();}}2.获取 validator 并校验 codeduidaima.compublic class TestValidator {// 注入验证器@Resourceprivate javax.validation.Validator validator;public String testMethod(TestRequest request) {// 进行校验,获取校验结果Set<ConstraintViolation<TestRequest>> constraintViolations = validator.validate(request);// 组装校验信息并返回return res;}}Dubbo 接口校验可在 @DubboService 注解中,设置 validation 参数为 true,开启生产者的字段验证。 codeduidaima.com@DubboService(version = "1.0.0", validation="true")public class DubboApiImpl implements DubboApi {....}该方式返回的信息对使用者不友好,可通过 Dubbo 的 filter 自定义校验逻辑和返回信息。需要注意的是,在 Dubbo 中有自己的 IOC 实现来控制容器,因此需提供 setter 方法,供 Dubbo 调用。 codeduidaima.com@Activate(group = {"provider"},value = {"customValidationFilter"},order = 10000)@Slf4jpublic class CustomValidationFilter implements Filter {private javax.validation.Validator validator;// duubo会调用setter获取beanpublic void setValidator(javax.validation.Validator validator) {this.validator = validator;}public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {if (this.validator != null && !invocation.getMethodName().startsWith("$")) {// 补充字段校验,返回信息的组装以及异常处理}return invoker.invoke(invocation);}}进阶使用分组校验对于同一个 DTO, 不同场景下对其校验规则可能不同, @Validted 支持按照分组分别验证,示例代码如下:1.校验注解的 groups 属性中添加分组 codeduidaima.com@Datapublic class NoticeDTO {@Min(value = 0L, groups = Update.class)private Long id;@NotNull(groups = {Save.class, Update.class})@Length(min = 2, max = 10, groups = {Save.class, Update.class})private String title;// 保存的时候校验分组public interface Save {}// 更新的时候校验分组public interface Update {}}2.@Validted 上指定分组 codeduidaima.com@PostMapping("/save")public Response<Boolean> saveNotice(@RequestBody @Validated(NoticeDTO.Save.class) NoticeDTO noticeDTO) {// 分组为Save.class的校验通过,执行后续逻辑return Response.ok();}@PostMapping("/update")public Response<Boolean> updateNotice(@RequestBody @Validated(NoticeDTO.Update.class) NoticeDTO noticeDTO) {// 分组为Update.class的校验通过,执行后续逻辑return Response.ok();}自定义校验注解如果我们想自定义实现一些验证逻辑,可以使用自定义注解,主要包括两部分:实现自定义注解,实现对应的校验器 validator。下面尝试实现一个注解,用于校验集合中的指定属性是否存在重复,代码如下:实现校验注解,主要需要包含 message()、 filed()、 groups() 三个方法,功能如注释所示。 codeduidaima.com@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documented// 指定校验器@Constraint(validatedBy = UniqueValidator.class)public @interface Unique {// 用于自定义验证信息String message() default "字段存在重复";// 指定集合中的待校验字段String[] field();// 指定分组Class<?>[] groups() default {};}实现对应的校验器,主要校验逻辑在 isValid 方法:获取集合中指定字段,并组装为 set,比较 set 和集合的长度,以判断集合中指定字段是否存在重复。 codeduidaima.com// 实现ConstraintValidator<T, R>接口,T为注解的类型,R为注解的字段类型public class UniqueValidator implements ConstraintValidator<Unique, Collection<?>> {private Unique unique;@Overridepublic void initialize(Unique constraintAnnotation) {this.unique = constraintAnnotation;}@Overridepublic boolean isValid(Collection collection, ConstraintValidatorContext constraintValidatorContext) {// 集合为空直接校验通过if (collection == null || collection.size() == 0) {return Boolean.TRUE;}// 从集合中获取filed中指定的待校验字段,看是否存在重复return Arrays.stream(unique.field()).filter(fieldName -> fieldName != null && !"".equals(fieldName.trim())).allMatch(fieldName -> {// 收集集合collection中字段为fieldName的值,存入set并计算set的元素个数countint count = (int) collection.stream().filter(Objects::nonNull).map(item -> {Class<?> clazz = item.getClass();Field field;try {field = clazz.getField(fieldName);field.setAccessible(true);return field.get(item);} catch (Exception e) {return null;}}).collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size));// set中元素个数count与集合长度比较,若不相等则说明collection中字段存在重复,校验不通过if (count != collection.size()) {return false;}return true;});}}总结通过本文我们得以了解 Spring Validation 的机理及其在实际项目中的应用。无论是标准的校验注解,还是自定义的校验逻辑, Spring Validation 都为开发者提供了高效且强大的校验工具。总的来说, Spring Validation 是任何 Spring 应用不可或缺的一部分,对于追求高质量代码的 JAVA 开发者而言,掌握其用法和最佳实践至关重要。转载自cid:link_0Group/Topic/JAVA/18022
  • [技术干货] 前端如何发送date类型的参数给后端
    @DateTimeFormat第一次:Get方式传参-成功 这个时候是用的get请求方式,get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应。/** * http://localhost:8080/intoParam?date=2023-01-18 11:11:11*/ @RequestMapping(value = "/intoParam",method = RequestMethod.GET) @ResponseBody public void intoParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date){ System.out.println(date);//Fri Jan 18 08:00:00 CST 2023 }第二次:Post方式传参-失败/** * http://localhost:8080/intoParam * 请求体 * { * "date":"2023-01-18 11:11:11" * } */ @RequestMapping(value = "/intoParam",method = RequestMethod.POST) @ResponseBody public void intoParam2(@RequestBody DateVo dateVo){ System.out.println(dateVo.getDate());//Fri Jan 18 08:00:00 CST 2023 }@Data@AllArgsConstructor@NoArgsConstructorpublic class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date date;}错误信息{ "timestamp": "2023-10-19T07:05:22.407+0000", "status": 400, "error": "Bad Request", "message": "JSON parse error: Cannot deserialize value of type `java.util.Date` from String \"2023-01-18 11:11:11\": not a valid representation (error: Failed to parse Date value '2023-01-18 11:11:11': Cannot parse date \"2023-01-18 11:11:11\": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String \"2023-01-18 11:11:11\": not a valid representation (error: Failed to parse Date value '2023-01-18 11:11:11': Cannot parse date \"2023-01-18 11:11:11\": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null))\n at [Source: (PushbackInputStream); line: 2, column: 12] (through reference chain: com.mye.hl20springbootdataparam.vo.DateVo[\"date\"])", "path": "/intoParam"}第三次:post传参-成功/** * http://localhost:8080/intoParam * 請求體是: * { * "date":"2023-01-18" * } */ @RequestMapping(value = "/intoParam",method = RequestMethod.POST) @ResponseBody public void intoParam2(@RequestBody DateVo dateVo){ System.out.println(dateVo.getDate());//Fri Jan 18 08:00:00 CST 2023 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String format = sdf.format(dateVo.getDate()); System.out.println(format);//2023-01-18 }@Data@AllArgsConstructor@NoArgsConstructorpublic class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd") private Date date;}原因springboot默认采用jackson,而jackson只能识别以下几种日期格式"yyyy-MM-dd'T'HH:mm:ss.SSSZ";"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";"yyyy-MM-dd"; "EEE, dd MMM yyyy HH:mm:ss zzz"; long类型的时间戳(毫秒时间戳)解决方法采用long时间戳,如:1537191968000或者在传参的对象上加上@JsonFormat注解并且指定时区。@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")第四次:post传参-成功/** * http://localhost:8080/intoParam * 請求體是: * { * "date":"2019-01-18 11:11:11" * } */ @RequestMapping(value = "/intoParam",method = RequestMethod.POST) @ResponseBody public void intoParam2(@RequestBody DateVo dateVo){ System.out.println(dateVo.getDate());//Fri Jan 18 11:11:11 CST 2019 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String format = sdf.format(dateVo.getDate()); System.out.println(format);//2019-01-18 11:11:11 }@Data@AllArgsConstructor@NoArgsConstructorpublic class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd") @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date date;}@jsonFormat@JsonFormat(pattern=“yyyy-MM-dd”,timezone = “GMT+8”)pattern:是你需要转换的时间日期的格式timezone:是时间设置为东八区,避免时间在转换中有误差@JsonFormat注解可以在属性的上方,同样可以在属性对应的get方法上,两种方式没有区别展示结果{ "date": "2023-10-19 15:44:51"}总结 前端Content-Type 为application/json的请求时,我们使用@JsonFormat来进行转化,如果为表单,则应该使用@DateTimeFormat。转载自https://www.duidaima.com/Group/Topic/JAVA/18030
  • [技术干货] cpu和内存的查看命令
    查看CPU相关命令#限制某个线程的cpu使用率sudo cpulimit -p pid -l 50ps -eo %cpu,args | grep -m1 PROCESS | awk '{print $1}'#将当前进程按照memory和cpu排序ps aux --sort=%mem,%cpu#按照cpu使用率排序ps -e -o pcpu,cpu,nice,state,cputime,args --sort pcpu | sed "/^ 0.0 /d"#查看当前系统的物理cpu个数grep "processor" /proc/cpuinfo | wc -l grep -c -e '^cpu[0-9]\+' /proc/stat#查看当前cpu型号grep "model name" /proc/cpuinfo#查看当前cpu信息cat /proc/cpuinfo#查看当前系统的位数grep -q '\<lm\>' /proc/cpuinfo && echo 64 bits || echo 32 bitsgetconf LONG_BIT | grep '64'java -version#查看当前系统的cpu频率awk -F": " '/cpu MHz\ */ { print "Processor (or core) running speed is: " $2 }' /proc/cpuinfo ; dmidecode | awk -F": " '/Current Speed/ { print "Processor real speed is: " $2 }'#查看每个cpu每个进程的cpu使用率ps ax -L -o pid,tid,psr,pcpu,args | sort -nr -k4| head -15 | cut -c 1-90#查看当前中断cat /proc/interrupts#查看多个处理器的使用率相关信息mpstat –P ALL 1#每个物理CPU中Core的个数:cat /proc/cpuinfo | grep "cpu cores" | uniq | awk -F: '{print $2}'#是否为超线程?#如果有两个逻辑CPU具有相同的”core id”,那么超线程是打开的。#每个物理CPU中逻辑CPU(可能是core, threads或both)的个数:cat /proc/cpuinfo | grep "siblings"#/proc/stat 文件中有一行记录的机器从启动依赖,各个中断序号发生中断的次数。#这一行以intr开头,接下来的第一个数字是总的中断数目,之后就是分别的中断数目,从0开始。cat /proc/stat | grep intr查找文件相关命令#按照目录大小排序战士最前面15个目录或者文件du -xB M --max-depth=2 /var | sort -rn | head -n 15#列出当前所有子目录的文件大小du -h --max-depth=1#列出当前文件或者目录最大的10个du -s * | sort -n | tail#按照目录大小从大到小排序du -b --max-depth 1 | sort -nr | perl -pe 's{([0-9]+)}{sprintf "%.1f%s", $1>=2**30? ($1/2**30, "G"): $1>=2**20? ($1/2**20, "M"): $1>=2**10? ($1/2**10, "K"): ($1, "")}e'#列出path这个目录的文件树du -h /path | sort -h#每隔60s监控对应目录的文件大小变化watch -n60 du /var/log/messages#递归删除当前目录下所有子目录中的.svn目录find . -type d -name '.svn' -print0 | xargs -0 rm -rdf#列出当前磁盘的使用情况df -P | column -t#监控磁盘的使用情况watch -d -n 5 df#列出当前inode的使用情况df -i <partition>#按照每个磁盘使用量从高到低排序df -h | grep -v ^none | ( read header ; echo "$header" ; sort -rn -k 5)#查看物理磁盘的使用情况df -x tmpfs | grep -vE "(gvfs|procbususb|rootfs)"#查看当前所有磁盘的大小和使用量df -H#查看所有分区使用情况fdisk -l /dev/sda# 显示系统所有的分区或给定的分区fdisk -l # 显示时,显示的是扇区数不是柱面数 fdisk -u # 显示指定partition的block数 fdisk -s partition #查看磁盘的读写容量iostat -m -d /dev/sda1#测试磁盘的读写速度hdparm -t /dev/sda#查看某个文件的所有链接find -L / -samefile /path/to/file -exec ls -ld {} +#查看最大的5个文件find . -type f -exec ls -s {} \; | sort -n -r | head -5#查看365天前的文件并删除find ./ -type f -mtime +365 -exec rm -f {} \;#查看大于100M的文件find . -type f -size +100M
  • [技术干货] 几种常见工具的功能对比和应用场景
    Flume是一种可靠、可扩展、分布式的日志收集、聚合和传输系统。它主要用于以下几个应用场景:1、日志收集:Flume能够从多个源头(例如服务器、应用程序、设备等)收集大量的日志数据,并将其中转到集中式存储系统(如Hadoop HDFS)或消息队列系统(如Kafka)中。这种集中式收集机制可以帮助企业对日志数据进行集中管理和分析。2、数据聚合:Flume可以将多个源头的数据聚合到一起,并将其传输到统一的目标系统。例如,在分布式计算中,可以使用Flume将不同节点上的数据聚合并传输到计算节点或结果节点。3、流式数据传输:Flume支持实时的、可靠的数据传输,因此被广泛用于构建数据流水线,以实现流数据的实时处理和分析。4、网络日志分析:Flume可以用于抓取并分析网络设备、服务器和应用程序产生的日志数据。通过配置适当的源和目标,Flume能够将日志数据从各个设备中收集、存储并进行分析。DataX CDC(Change Data Capture)的主要应用场景有以下几个:1、数据同步:将源数据库中的数据变更(增、删、改)实时同步到目标数据库,确保两个数据库中的数据保持一致。2、数据仓库加载:将源数据库中的增量数据加载到数据仓库或数据湖中,用于数据分析、报表生成等业务需求。3、数据备份与恢复:通过记录数据变更,实时备份源数据库中的增量数据,以防止数据库故障或数据丢失,同时可以快速恢复到指定的时间点。4、实时数据分析:将源数据库中的增量数据实时传输给大数据平台,用于实时数据分析、实时监控等业务场景。5、数据集成和ETL:将多个数据源中的数据变更集成到一个目标数据库中,实现数据的统一管理和分析。需要注意的是,DataX CDC是一款开源数据同步工具,能够实现增量数据的抽取和传输,但并不负责处理数据转换和清洗的任务。Flink CDC(Change Data Capture)的主要应用场景有以下几个:1、实时数据分析:Flink CDC可以将源数据库中的增量数据实时传输到Flink流处理引擎中,使得实时数据分析和实时计算成为可能。通过实时处理增量数据,可以进行实时指标计算、实时报表生成、实时业务监控等。2、数据协同与集成:Flink CDC可以将多个不同数据源的增量数据集成到一个目标系统中,实现不同数据源之间的数据协同和集成。比如将多个数据库中的数据变更实时同步到数据仓库或数据湖,以实现统一的数据分析和报表生成。3、数据流转与传输:Flink CDC可以将源数据库中的增量数据转换成数据流,并实时传输到指定的目标位置。这在数据实时传输和数据交换场景中非常有用,比如将数据传输到消息队列、实时推送数据给其他系统等。4、实时数据湖建设:通过Flink CDC将源数据库中的增量数据实时写入数据湖中,可以建立起一个实时的数据湖,进而支持实时分析、实时机器学习等高价值数据应用。5、实时数据缓存和缓冲:Flink CDC可以实时捕获和缓冲源数据库中的增量数据,并加快其它系统对数据的访问速度,减少对数据库的直接查询和压力。Kettle(也称为Pentaho Data Integration)是一个开源的ETL工具,主要用于数据抽取(Extract)、转换(Transform)和加载(Load)操作。下面是Kettle主要的应用场景:1、数据同步:Kettle可以将不同数据源(如关系型数据库、文件、Web服务等)中的数据进行抽取和同步,实现数据的一致性和更新。2、数据仓库加载:Kettle可以将数据从各种数据源加载到数据仓库(如数据仓库、数据湖等),并进行数据清洗、转换和映射操作,以支持后续的分析和报告需求。3、实时数据分析:Kettle可以实时抽取和转换数据,将其加载到实时数据分析平台中,实现实时监控、实时分析和实时决策。4、数据质量管理:Kettle提供了各种数据清洗和校验的功能,可以帮助用户识别和修复数据质量问题,提高数据的准确性和完整性。  以下是相关同步工具的功能对比:转载自https://www.studyjava.cn/post/2029
  • [技术干货] Kettle 的工作原理和优势
    Kettle,也被称为Pentaho Data Integration(PDI),是一款开源的ETL工具,用于提取、转换和加载数据。它的工作原理可以简单概括为以下几个步骤:1、设计和配置:在Kettle的图形化界面中,通过拖拽和连接各个组件,设计数据转换和加载的流程。每个组件代表不同的任务,如数据抽取、数据清洗、数据转换、数据加载等。为每个组件配置相应的参数,定义数据输入输出的连接和转换规则。2、读取数据:Kettle支持多种数据来源,如关系型数据库、文件、Web服务等。根据配置的数据源和查询条件,Kettle会读取数据源中的数据。3、数据转换和清洗:通过在转换和清洗组件中定义规则和函数,对读取的数据进行处理和转换。可以进行各种操作,如过滤、排序、合并、聚合、计算等,以满足数据处理的需求。4、加载数据:将经过转换和清洗的数据加载到目标数据源中。可以是关系型数据库、文件、数据仓库等。Kettle提供了各种输出组件,用于将数据写入到目标数据源中。5、执行和调度:完成数据处理流程的配置后,可以手动执行该流程。也可以使用Kettle内置的调度器,按照预定的时间和频率自动运行任务。总的来说,Kettle通过可视化的方式,串联和配置多个数据处理和转换组件,实现数据的提取、转换和加载。它具有强大的扩展性和灵活性,能够满足各种数据集成和处理的需求。优势:1、强大的可视化设计和配置:Kettle提供了一个易于使用的图形化界面,使用户可以轻松设计和配置ETL工作流程,而无需编写复杂的代码。2、多种数据源和目标的支持:Kettle可以与各种数据源(例如数据库、文件、Web服务等)以及数据目标(例如数据库、文件、Data Warehouse等)进行无缝集成,方便数据的抽取和加载。3、强大的数据转换和清洗功能:Kettle提供了丰富的数据转换和清洗步骤,如字段映射、数据过滤、数据合并、数据拆分、数据排序等,可以灵活地处理和转换数据。4、插件扩展机制:Kettle支持插件机制,用户可以通过自定义插件扩展Kettle的功能,满足特定需求。5、支持任务调度和并行处理:Kettle可以根据需求进行任务调度和并行处理,提高ETL工作的效率和可靠性。劣势:1、学习曲线较陡峭:尽管Kettle提供了图形化界面,但对于没有经验的用户来说,学习和掌握Kettle的各种功能和操作仍然需要一定的时间和精力。2、对大规模数据处理的限制:由于Kettle是基于Java开发的,对于大规模数据的处理和性能可能存在一些限制,特别是在并行处理和集群环境下。3、对于复杂数据处理场景的限制:尽管Kettle提供了丰富的数据转换和清洗步骤,但在处理复杂数据处理场景时,可能需要编写自定义的脚本或插件来实现。Kettle 支持的实时采集数据源类型:转载自https://www.studyjava.cn/post/2029
  • [技术干货] DataX CDC的工作原理和优势
    DataX CDC基于DataX框架,为用户提供了一种灵活、高效的数据同步解决方案。它通过监视源数据库的事务日志或数据库增量日志来捕获源数据库中的变更操作,并将这些操作应用于目标数据库,以保持两者之间的数据同步。这种增量方式可以大大减少数据传输的时间和成本,并提供更及时的数据更新。DataX CDC 是基于 CDC(Change Data Capture)技术实现的数据同步工具,其工作原理如下:1、数据源监控:DataX CDC 首先会监控数据源(如 Oracle 数据库)的事务日志。事务日志是数据库记录每个操作(如插入、更新、删除)的日志文件。2、数据解析:一旦有新的事务日志生成,DataX CDC 会解析事务日志,提取出新增、更新和删除的数据。3、数据同步:经过解析后,DataX CDC 将提取到的数据进行转换和转发,将其同步到目标数据源(如 HDFS、MySQL 等)。4、数据应用:目标数据源接收到同步的数据后,可以被应用程序直接使用或者进行进一步的处理和分析。具体举例来说,DataX CDC 监控 Oracle 事务日志的步骤如下:1、配置数据库连接:首先,需要在 DataX CDC 中配置 Oracle 数据库的连接信息,包括数据库地址、用户名、密码等。2、开启 CDC 日志模式:在 Oracle 数据库中,需要将数据库的日志模式设置为 CDC 日志模式。这可以通过在 Oracle 数据库中执行相应的 SQL 命令来完成,例如执行 ALTER DATABASE ADD SUPPLEMENTAL LOG DATA 命令。3、配置 Change Data Capture:在 DataX CDC 中,需要配置相应的 Change Data Capture 模块,以便能够捕获 Oracle 数据库中的变更事件。这包括指定表的 CDC 规则、列的映射关系等。4、监控事务日志:一旦配置完成,DataX CDC 将自动监控 Oracle 数据库的事务日志,捕获其中的数据变更事件。5、数据同步:捕获到的数据变更事件将会被传递给目标系统或者工具,以完成数据同步操作。注意,在使用 DataX CDC 监控 Oracle 事务日志时,需要确保数据库的参数正确配置,并且有足够的权限执行相关的 SQL 命令。此外,由于监控事务日志可能会带来额外的性能开销,因此需要根据实际情况进行调整和优化。优势:1、实时同步:DataX CDC 可以实时监控和同步数据源的变更,能够及时将数据更新到目标数据源,提供高实时性的数据同步。2、高性能:DataX CDC 使用 CDC 技术,只同步变更数据,避免了全量数据的传输和处理,能够提供高效的数据同步性能。3、精确同步:DataX CDC 可以精确捕获和同步数据源的每一次变更操作,保证了数据的一致性和准确性。4、灵活配置:DataX CDC 提供了灵活的配置选项,可以根据具体需求选择要同步的数据源、目标数据源等,并支持多种数据源类型。劣势:1、复杂性:DataX CDC 的配置和使用相对复杂,需要熟悉 CDC 技术和相关的配置知识。2、依赖数据库事务日志:DataX CDC 的工作原理是基于数据库事务日志的,因此需要确保数据源开启了事务日志功能,并且保证事务日志的稳定性和完整性。3、对数据库性能有影响:在进行实时同步的过程中,DataX CDC 需要读取和解析数据库的事务日志,可能会对源数据库的性能产生一定影响。DataX CDC 支持的实时采集数据源类型:转载自https://www.studyjava.cn/post/2029
  • [技术干货] Flink CDC的工作原理和优势
    Flink CDC通过与数据库进行交互,实时捕获数据库中的变更操作。它的工作原理可以分为以下几个步骤:1. 数据库连接和监控:首先,Flink CDC需要与目标数据库建立连接,并监控数据库的变更操作。它可以通过监听数据库的事务日志或者使用数据库引擎的内部机制来实现。2. 变更事件解析:一旦数据库发生变更操作,Flink CDC会解析这些变更事件。它会将变更事件转化为对应的数据结构,例如INSERT、UPDATE或DELETE操作。3. 数据转换和格式化:解析后的变更事件需要经过数据转换和格式化,以便能够被Flink进行处理。Flink CDC会将变更事件转化为Flink支持的数据格式,例如JSON、Avro等。4. 事件流生成:经过转换和格式化后,Flink CDC会将变更事件转化为数据流。这个数据流可以被Flink的流处理任务进行消费和处理。5. 数据同步和传输:生成的数据流可以被传输到不同的目的地,例如Flink的流处理任务、消息队列或者其他外部系统。这样,我们就可以对变更事件进行实时分析和处理。需要注意的是,Flink CDC并非直接支持所有数据库。它的可用性取决于数据库本身是否提供了事务日志的访问接口。目前,Flink CDC支持的数据库包括MySQL、PostgreSQL、Oracle等。优势:1. 实时性:Flink CDC能够实时捕获数据库中的变更操作,并将其转化为实时的数据流。这使得我们能够及时地对数据库中的数据变动进行响应和处理。2. 精确性:Flink CDC通过解析数据库的事务日志或者内部机制,能够准确地捕获数据库中的变更操作。这保证了数据的准确性和一致性。3. 可靠性:Flink CDC能够处理数据库中的变更操作,并将其转化为可靠的数据流。它具有容错机制,能够处理故障和数据丢失的情况。4. 扩展性:Flink CDC支持水平扩展,能够处理大规模的数据变更。它可以与Flink的流处理任务无缝集成,实现高效的数据处理和分析。劣势:1.对于非关系型数据库(如MongoDB、HBase等)的支持相对较弱。Flink CDC主要面向关系型数据库,对于非关系型数据库的支持相对有限。2.对于大规模数据变更的处理可能存在延迟。由于Flink CDC通过读取事务日志来捕获数据变化,如果有大量的数据变更发生,可能会造成读取和处理的延迟。3.需要访问数据库的主从部署。为了保持数据一致性,Flink CDC需要访问数据库的主库以读取事务日志,这可能会对数据库的性能产生一定影响,尤其是在高并发的情况下。4.需要数据库的binlog开启。Flink CDC依赖数据库的binlog来捕获数据变化,如果数据库的binlog没有开启,就无法正常使用Flink CDC。需要注意的是,这些劣势并不是Flink CDC本身的问题,而是基于流式数据捕获的一般限制。如果你的应用场景不适合流式数据捕获,可能需要考虑其他的数据同步方案。Flink CDC 支持的实时采集数据源类型:转载自https://www.studyjava.cn/post/2029
  • [技术干货] Flume的工作原理和优势
    Flume的原理是将数据从源头(source)采集,经过一个或多个中间节点(channel)进行传输,最终发送到目的地(sink)。下面是Flume的工作原理的详细解释:1、Source(数据源):Source负责从数据源(如日志文件、网络流、消息队列等)采集数据,并将数据发送到Channel。2、Channel(通道):Channel是数据传输的中间节点,它负责暂存Source采集到的数据。Channel可以是内存、磁盘或者其他存储介质,可以按照事务或批量的方式传输数据。3、Sink(数据目的地):Sink从Channel中获取数据,并将数据发送到指定的目的地(如HDFS、关系型数据库、消息队列等)进行存储和分析。4、Agent(代理):Agent是Flume的一个独立运行实例,它由Source、Channel和Sink组成,负责管理整个数据流的采集、传输和存储。Flume的工作流程如下:1、Source采集数据,将数据发送到Channel。2、Channel将数据暂存,并等待Sink的消费。3、Sink从Channel中获取数据,发送到目的地进行存储和分析。Flume的可靠性和容错性体现在以下几个方面:1、消息确认:Channel会对消息进行确认,确保数据在传输过程中不会丢失。2、事务机制:Flume使用事务机制来确保数据的可靠传输,如果发送失败,会进行回滚和重试操作。3、失败处理:Flume提供了失败处理机制,可以配置重试策略、错误日志记录等来处理发送失败的情况。总的来说,Flume通过源头采集数据,经过中间节点传输,最后发送到目的地实现数据的收集和传输。通过可靠的机制和丰富的配置选项,可以保证数据的安全和可靠传输。优势:1、可靠性高:Flume 采用了可靠性机制,包括数据重传、事件推送确认机制等,确保数据不会丢失。2、扩展性强:Flume 可以通过添加多个代理节点实现横向扩展,使其可以处理大规模的数据流。3、灵活性:Flume 可以根据不同的需求进行配置,支持多种收集器、聚合器和传输器,可以满足不同场景的需求。4、实时数据传输:Flume 支持实时数据传输,可以以流式方式处理和传输数据,适用于需要实时数据处理和分析的场景。5、分布式流处理:Flume 支持分布式架构,可以将数据流分发到多个节点上进行并行处理和传输。劣势:1、复杂性:Flume 的配置和部署相对复杂,需要一定的技术功底和经验。2、依赖性:Flume 依赖于其他组件,比如 Hadoop、HBase 等,如果没有这些组件的支持,应用可能会受到限制。3、部署和维护成本:由于 Flume 的复杂性和依赖性,部署和维护的成本可能较高。Flume支持的实时采集数据源类型:转载自https://www.studyjava.cn/post/2029
  • [常见问题汇总帖] copyManager方法导入文本数据,数据有回车换行符导致导入不了
    copyManager文本导入数据有\r\n导致文本里的一条数据却分成了多行,其实实际就是一条数据,因为\r\n影响导致。请问要怎么处理 用的copyIn方法,能否回答具体点,新手,第一次用copyManager,很不熟悉,能贴出代码最好,感谢。。是不是copy xxx from STDIN后面得跟什么参数配置?
  • [技术干货] java Stream编程笔记
    Stream介绍Java Stream 的主要作用有以下几个方面:简化集合操作:使用传统的 for 循环或迭代器来处理集合数据可能会导致冗长而复杂的代码。延迟计算:流式操作允许你在处理数据之前定义一系列的操作步骤,但只在需要结果时才会实际执行。这种延迟计算的特性意味着可以根据需要动态调整数据处理的操作流程,提升效率。并行处理:Java Stream 提供了并行流的支持,可以将数据分成多个块进行并行处理,从而充分利用多核处理器的性能优势,提高代码的执行速度。函数式编程风格:流式编程鼓励使用函数式编程的思想,通过传递函数作为参数或使用 Lambda 表达式来实现代码的简化和灵活性。为什么使用流式编程可以提高代码可读性和简洁性声明式编程风格:流式编程采用了一种声明式的编程风格,你只需描述你想要对数据执行的操作,而不需要显式地编写迭代和控制流语句。链式调用:流式编程使用方法链式调用的方式,将多个操作链接在一起。每个方法都返回一个新的流对象,这样你可以像“流水线”一样在代码中顺序地写下各种操作,使代码逻辑清晰明了。操作的组合:流式编程提供了一系列的操作方法,如过滤、映射、排序、聚合等,这些方法可以按照需要进行组合使用。你可以根据具体的业务需求将这些操作串联起来,形成一个复杂的处理流程,而不需要编写大量的循环和条件语句。减少中间状态:传统的迭代方式通常需要引入中间变量来保存中间结果,这样会增加代码的复杂度和维护成本。而流式编程将多个操作链接在一起,通过流对象本身来传递数据,避免了中间状态的引入。减少循环和条件:流式编程可以替代传统的循环和条件语句的使用。什么是 Stream?Stream(流)是一个来自数据源的元素队列并支持聚合操作元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。不可变性:Stream 是不可变的,它不会修改原始数据源,也不会产生中间状态或副作用。每个操作都会返回一个新的流对象,以保证数据的不可变性。 和以前的Collection操作不同, Stream操作还有两个基础的特征:Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。Stream中的操作大体可以分为两类中间操作:将流一层层的进行处理,并向下一层进行传递,如 filter map sorted等。 中间操作又分为有状态(stateful)及无状态(stateless)有状态:必须等上一步操作完拿到全部元素后才可操作,如sorted无状态:该操作的数据不收上一步操作的影响,如filter map终止操作:触发数据的流动,并收集结果,如collect findFirst forEach等。终止操作又分为短路操作(short-circuiting)及非短路操作(non-short-circuiting)短路操作:会在适当的时刻终止遍历,类似于break,如anyMatch findFirst等非短路操作:会遍历所有元素,如collect max等Stream中间操作过滤操作(filter)过滤操作(filter),它接受一个 Predicate 函数作为参数,用于过滤 Stream 中的元素。只有满足 Predicate 条件的元素会被保留下来,而不满足条件的元素将被过滤掉。过滤操作的语法如下Stream<T> filter(Predicate<? super T> predicate)其中,T 表示 Stream 元素的类型,predicate 是一个函数式接口 Predicate 的实例,它的泛型参数和 Stream 元素类型一致,并且predicate返回的值必须是boolean类型,因为需要通过真假值判断是否要过滤该值。使用过滤操作可以根据自定义的条件来筛选出符合要求的元素,从而对 Stream 进行精确的数据过滤。下面是一个示例,演示如何使用过滤操作筛选出一个整数流中的大于三的数: List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 生成流式对象 // Stream<Integer> stream = numbers.stream(); List<Integer> list=numbers.stream().filter( (x)->{return x>3;} //lambda表达式 可简化成下面的写法 // x->x>3 ).toList(); System.out.println("number中大于3的数: "+list.toString());映射操作(map)映射操作(map),它接受一个 Function 函数作为参数,用于对 Stream 中的每个元素进行映射转换,生成一个新的 Stream。映射操作的语法如下:<R> Stream<R> map(Function<? super T, ? extends R> mapper)其中,T 表示原始 Stream 的元素类型,R 表示映射后的 Stream 的元素类型,mapper 是一个函数式接口 Function 的实例,可以进行不同的映射操作.下面是一个示例,演示如何使用映射操作将一个字符串流中的每个字符串转换为其长度: List<String> numbers = Arrays.asList("apple", "banana", "cherry"); // 生成流式对象 // Stream<Integer> stream = numbers.stream(); numbers.stream().map( String::length ).forEach(System.out::println);在这个示例中,我们首先创建了一个包含字符串的 Stream,并调用 map() 方法传入String::length,表示要将每个字符串转换为其长度。然后通过 forEach() 方法遍历输出结果。注意: 映射操作可能引发空指针异常(NullPointerException),因此在执行映射操作时,应确保原始 Stream 中不包含空值,并根据具体情况进行空值处理。排序操作(sorted)排序操作(sorted)是 Stream API 中的一种常用操作方法,它用于对 Stream 中的元素进行排序。排序操作可以按照自然顺序或者使用自定义的比较器进行排序。排序操作的语法如下:Stream<T> sorted() Stream<T> sorted(Comparator<? super T> comparator)第一种语法形式中,sorted() 方法会根据元素的自然顺序进行排序。如果元素实现了 Comparable 接口并且具备自然顺序,那么可以直接调用该方法进行排序。第二种语法形式中,sorted(Comparator<? super T> comparator) 方法接受一个比较器(Comparator)作为参数,用于指定元素的排序规则。通过自定义比较器,可以对非 Comparable 类型的对象进行排序。下面是一个示例,演示如何使用排序操作对一个字符串流进行排序: List<String> numbers = Arrays.asList("apple", "banana", "cherry"); // 生成流式对象 // Stream<Integer> stream = numbers.stream(); numbers.stream().sorted().forEach(System.out::println);输出apple banana cherry注意: 排序操作可能会影响程序的性能,特别是对于大型数据流或者复杂的排序规则。因此,在实际应用中,需要根据具体情况进行权衡和优化,选择合适的算法和数据结构来提高排序的效率。截断操作(limit 和 skip)截断操作(limit和skip),用于在处理流的过程中对元素进行截断。limit(n):保留流中的前n个元素,返回一个包含最多n个元素的新流。如果流中元素少于n个,则返回原始流。skip(n):跳过流中的前n个元素,返回一个包含剩余元素的新流。如果流中元素少于n个,则返回一个空流。 下面分别详细介绍这两个方法的使用。注意: 在使用截断操作时需要注意流的有界性。如果流是无界的(例如 Stream.generate()),那么使用 limit() 方法可能导致程序陷入无限循环,而使用 skip() 方法则没有意义。Stream 的终止操作forEach 和 peekforEach和peek都是Stream API中用于遍历流中元素的操作方法,它们在处理流的过程中提供了不同的功能和使用场景。forEach: forEach是一个终端操作方法,它接受一个Consumer函数作为参数,对流中的每个元素执行该函数。forEach会遍历整个流,对每个元素执行相同的操作。示例代码:List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .forEach(System.out::println);peek: peek是一个中间操作方法,它接受一个Consumer函数作为参数,对流中的每个元素执行该函数。与forEach不同的是,peek方法会返回一个新的流,该流中的元素和原始流中的元素相同。示例代码:List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .peek(System.out::println) .toList();聚合操作(reduce)reduce和collect都是Stream API中用于聚合操作的方法,它们可以将流中的元素进行汇总、计算和收集。reduce: reduce是一个终端操作方法,它接受一个BinaryOperator函数作为参数,对流中的元素逐个进行合并操作,最终得到一个结果。该方法会将流中的第一个元素作为初始值,然后将初始值与下一个元素传递给BinaryOperator函数进行计算,得到的结果再与下一个元素进行计算,以此类推,直到遍历完所有元素。示例代码: List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); numbers.stream().reduce(Integer::sum).ifPresent(System.out::println);//输出结果15在这个示例中,我们创建了一个包含整数的List,并通过stream()方法将其转换为流。然后使用reduce方法对流中的元素进行求和操作,将每个元素依次相加,得到结果15。匹配操作(allMatch、anyMatch 和 noneMatch)在 Stream API 中,allMatch、anyMatch 和 noneMatch 是用于进行匹配操作的方法,它们可以用来检查流中的元素是否满足特定的条件。allMatch: allMatch 方法用于判断流中的所有元素是否都满足给定的条件。当流中的所有元素都满足条件时,返回 true;如果存在一个元素不满足条件,则返回 false。anyMatch: anyMatch 方法用于判断流中是否存在至少一个元素满足给定的条件。当流中至少有一个元素满足条件时,返回 true;如果没有元素满足条件,则返回 false。在这个示例中,我们创建了一个包含整数的 List,并通过 stream() 方法将其转换为流。然后使用 anyMatch 方法判断流中是否存在偶数。由于列表中存在偶数,所以返回 true。noneMatch: noneMatch 方法用于判断流中的所有元素是否都不满足给定的条件。当流中没有元素满足条件时,返回 true;如果存在一个元素满足条件,则返回 false。示例代码:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); boolean allEven = numbers.stream() .allMatch(n -> n % 2 == 0); System.out.println(allEven); // 输出结果: false boolean hasEven = numbers.stream() .anyMatch(n -> n % 2 == 0); System.out.println(hasEven); // 输出结果: true boolean noneNegative = numbers.stream() .noneMatch(n -> n < 0); System.out.println(noneNegative); // 输出结果: true查找操作(findFirst 和 findAny)在 Stream API 中,findFirst 和 findAny 是用于查找操作的方法,它们可以用来从流中获取满足特定条件的元素。findFirst: findFirst 方法用于返回流中的第一个元素。它返回一个 Optional 对象,如果流为空,则返回一个空的 Optional;如果流非空,则返回流中的第一个元素的 Optional。findAny: findAny 方法用于返回流中的任意一个元素。它返回一个 Optional 对象,如果流为空,则返回一个空的 Optional;如果流非空,则返回流中的任意一个元素的 Optional。在顺序流中,通常会返回第一个元素;而在并行流中,由于多线程的处理,可能返回不同的元素。示例代码:List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Optional<String> first = names.stream() .findFirst(); first.ifPresent(System.out::println); // 输出结果: Alice List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> any = numbers.stream() .filter(n -> n % 2 == 0) .findAny(); any.ifPresent(System.out::println); // 输出结果: 2 或 4(取决于并行处理的结果)统计操作(count、max 和 min)在 Stream API 中,count、max 和 min 是用于统计操作的方法,它们可以用来获取流中元素的数量、最大值和最小值。count: count 方法用于返回流中元素的数量。它返回一个 long 类型的值,表示流中的元素个数。max: max 方法用于返回流中的最大值。min: min 方法用于返回流中的最小值。它返回一个 Optional 对象,如果流为空,则返回一个空的 Optional;如果流非空,则返回流中的最小值的 Optional。示例代码:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); long count = numbers.stream() .count(); System.out.println(count); // 输出结果: 5 Optional<Integer> max = numbers.stream() .max(Integer::compareTo); max.ifPresent(System.out::println); // 输出结果: 5 Optional<Integer> min = numbers.stream() .min(Integer::compareTo); min.ifPresent(System.out::println); // 输出结果: 1
  • [技术干货] 物联网协议概述-转载
     前言 物联网是在互联网的基础上延伸和扩展的一种网络,其用户端延伸和扩展到了任何物品之间,彼此进行信息交换和通信,目的是实现所有物品与网络的连接,从而方便识别、管理和控制。  无线物联网的特点包括:全面感知、实时准确传递物品信息、利用智能计算技术对海量数据进行分析和处理,以实现智能化控制。  由于物联网中的很多设备都是资源受限型的,即只有少量的内存空间和有限的计算能力,所以传统的 HTTP 协议应用在物联网上就显得过于庞大而不适用。  MQTT 协议和 CoAP 协议都是物联网中比较流行的协议,都对传输量做了很大的精简,传输开销小,以适应物理网的网络环境。  内容 CoAP(Constrained Application Protocol 受限应用协议)  CoAP 是 6LowPAN 协议栈中的应用层协议。COAP 协议网络传输层由 TCP 改为 UDP。 COAP 是二进制格式的,HTTP 是文本格式的,COAP 比 HTTP 更加紧凑。 轻量化,COAP 最小长度仅仅 4B,一个 HTTP 的头都几十个 B 了。 支持可靠传输,数据重传,块传输。 确保数据可靠到达。 支持 IP 多播,即可以同时向多个设备发送请求。 非长连接通信,适用于低功耗物联网场景。  MQTT(Message Queuing Telemetry Transport 消息队列遥测传输)  为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布 / 订阅型消息协议。轻量、简单、开放和易于实现。 1. 使用发布 / 订阅消息模式,提供一对多的消息发布,解除应用程序耦合 2. 使用 TCP/IP 提供网络连接 3. 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量  这些特点使它适用于受限环境。例如,但不仅限于此: ・特别适合于网络代价昂贵,带宽低、不可靠的环境 ・能在处理器和内存资源有限的嵌入式设备中运行 ・使用发布 / 订阅消息模式,提供一对多的消息发布,从而解除应用程序耦合 ・使用 TCP/IP 提供网络连接 ・提供 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制  哪种协议?从应用场景要求的层面分析如何选用 1、服务端主动发送给客户端的数据(反控)的时效性(如通过 APP 控制硬件动作)          因 MQTT 协议是保持连接的,所以及时性相对很好;CoAP 协议是无连接响应式通讯,因此不能主动推送,要等客户端访问才可以携带回去,及时性相对较差。  2、设备环境对底层协议的要求或限制    MQTT 协议是建立在 TCP 协议基础之上的,因此他也具备 TCP 协议的优缺点;CoAP 协议是建立在 UDP 协议基础之上的,因此他也具备 UDP 协议的优缺点。  3、在 NAT 网络环境中是否需要调整    因 MQTT 协议是保持长连接的,所以在 NAT(Network Address Translation,网络地址转换)下没有问题;CoAP 协议因是无连接方式,需要使用 NAT 穿透性手段。  NAT NAT(Network Address Translation,网络地址转换)是 1994 年提出的。当在专用网内部的一些主机本来已经分配到了本地 IP 地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用 NAT 方法。这种方法需要在专用网连接到因特网的路由器上安装 NAT 软件。装有 NAT 软件的路由器叫做 NAT 路由器,它至少有一个有效的外部全球 IP 地址。这样,所有使用本地地址的主机在和外界通信时,都要在 NAT 路由器上将其本地地址转换成全球 IP 地址,才能和因特网连接。  4、实现多对多的通信还是单对单通信    因 MQTT 协议的消息模型是发布/订阅式的,所以是可以多对多通信的;CoAP 协议的消息模型是请求 / 响应式的,所以是单对单通信。  5、服务质量等级及自动重连重发    因 MQTT 协议有 QoS 配置,支持服务质量等级和自动重连重发机制;CoAP 本身不具备,需要应用层自己来写这个逻辑。      6、对网络稳定性要求    MQTT 协议利用自动重连重发机制解决网络不稳定问题,断网就会触发重连;CoAP 协议只有客户端发送的时候需要保证网络连接正常,其他时段无需连接。  7、对硬件设备的功耗影响    MQTT 协议因有保持连接,所以功耗略高于 CoAP 协议。 ———————————————— 版权声明:本文为CSDN博主「YF云飞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/flyTie/article/details/126186579 
  • [技术干货] 【Java小知识点】类加载器的区别-转载
     在Java中,类加载器(Class Loader)根据其加载类的来源和工作方式可以分为不同的分类。主要的类加载器分类如下:  启动类加载器(Bootstrap Class Loader):  启动类加载器是Java虚拟机的一部分,它负责加载Java平台核心库(Java API的一部分,如java.lang、java.util等)。 它是虚拟机本身的一部分,通常由C++编写,不是一个Java对象,也不继承java.lang.ClassLoader。 扩展类加载器(Extension Class Loader):  扩展类加载器负责加载Java的扩展库(Java标准库之外的一些功能,位于jre/lib/ext目录下)。 通常使用Java编写,是标准的ClassLoader的子类。 应用程序类加载器(Application Class Loader):  应用程序类加载器是最常用的加载器,负责加载应用程序的类,包括应用程序自身的类和第三方类库。 通常是系统类加载器的子类,由Java编写。 自定义类加载器:  开发者可以创建自定义类加载器,以实现特定的需求,例如从网络加载类、加密类文件等。 自定义类加载器需要继承java.lang.ClassLoader类,并覆盖其中的方法,例如findClass和loadClass。 另外,还可以根据加载类的顺序将类加载器分为以下三个主要类别:  双亲委派模型:  Java类加载器遵循双亲委派模型,即每个类加载器在尝试加载一个类之前,会先委派给其父加载器。这种模型有助于确保类加载的一致性和安全性。 如果父加载器找不到类,子加载器才会尝试加载。这意味着一些核心库类由启动类加载器加载,而应用程序类由应用程序类加载器加载。 扩展类加载器和应用程序类加载器:  扩展类加载器和应用程序类加载器通常是系统类加载器的子类,它们负责加载Java类库和应用程序的类。 这两个加载器通常是自定义类加载器的父加载器,因此开发者可以扩展它们以实现自定义加载行为。 自定义类加载器:  自定义类加载器允许开发者根据需要实现特定的加载行为,可以加载网络上的类、加密的类文件等。 自定义类加载器通常不是双亲委派模型中的一部分,开发者需要自行实现加载逻辑。 这些类加载器的分类和加载机制在Java应用程序的类加载过程中起到重要的作用,确保类能够正确加载和协同工作。 ———————————————— 版权声明:本文为CSDN博主「边境矢梦°」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/dandelionl_/article/details/133934828 
总条数:692 到第
上滑加载中