当前位置: 首页 > news >正文

手写Spring-MVC之前后置处理器与异常处理、数据库框架

Day48

手写Spring-MVC之前后置处理器与异常处理

前后置处理器

概念:从服务器获取的JSON数据可能是加密后的,因此服务端获取的时候需要进行解密(前置处理器)。

而从服务器传出的JSON数据可能需要加密,因此需要在处理返回值的时候进行加密(后置处理器)。

思路:

首先搭建前后置处理器的框架

和是否处理JSON格式的数据类似,需要根据注解判断controller层中的方法是否需要对JSON格式的数据进行解密或者返回数据进行加密,因此要添加两个注解@BeforeAdviser和@AfterAdviser

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeforeAdviser {
}@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AfterAdviser {
}

controller层的方法需要添加相应的注解标识

/*** 使用postman发送请求* url:http://localhost:8080/user/test12.action* json:{"username":"zs","password":"123123"}* 传递JSON参数和返回JSON*/@RequestMapping("/test12.action")@ResponseBody@AfterAdviserpublic User test12(@RequestBody @BeforeAdviser User user){System.out.println("user对象:"+ user);return user;

同时需要在参数描述类中和方法描述类中添加是否有相应注解的属性:

/*** 参数描述类
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ParameterDefinition {private String name;//参数名private Class<?> clazz;//参数类型private int index;//参数下标private Type[] actualTypeArguments;//参数泛型的数组private boolean requestBodyHasOrNot;//参数上是否有@RequestBody注解private boolean beforeAdviserHasOrNot;//参数上是否有@BeforeAdviser注解}/*** 方法描述类
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MethodDefinition {private String requestMappingPath;//子级URiprivate String name;//方法名private Method method;//方法对象private Class<?> returnClazz;//返回值类型private List<ParameterDefinition> parameterDefinitions;//参数描述类对象的集合private boolean responseBodyHasOrNot;//方法上是否有@ResponseBody注解private boolean afterAdviserHasOrNot;//方法上是否有@AfterAdviser注解
}

添加后监听器中封装部分也要进行相应修改:

//获取参数上是否有@BeforeAdviser注解
boolean beforeAdviserHasOrNot = false;
BeforeAdviser beforeAdviser = parameters[i].getAnnotation(BeforeAdviser.class);
if(beforeAdviser!=null){beforeAdviserHasOrNot = true;
}ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, parameterType, index,actualTypeArguments,requestBodyHasOrNot,beforeAdviserHasOrNot);//封装参数描述类对象
parameterList.add(parameterDefinition);
//获取方法上是否有@AfterAdviser注解
boolean afterAdviserHasOrNot = false;
AfterAdviser afterAdviser = method.getAnnotation(AfterAdviser.class);
if(afterAdviser!=null){afterAdviserHasOrNot = true;
}
MethodDefinition methodDefinition = new MethodDefinition(sonUri, methodName, method, returnType, parameterList,responseBodyHasOrNot,afterAdviserHasOrNot);//封装方法描述类对象

至此,监听器就能把信息记录下来,调度的DispatcherServlet进行工作的时候就可以获取到相应的注解信息。

前置处理器的使用是在获取参数类型的时候会判断是否有@BeforeAdviser注解,如果有则代表需要进行解密操作:

//解密
if(parameterDefinition.isBeforeAdviserHasOrNot()){//在这里进行具体的解密操作吗?
}

在处理返回值的时候会判断方法是否有@AfterAdviser注解,如果有则代表需要进行加密操作:

//加密
if(methodDefinition.isAfterAdviserHasOrNot()){//在这里进行加密操作吗?
}

进行具体的解密和加密操作:

前后置处理器的框架搭建好之后,会发现一个问题,如果在DispatcherServlet中进行具体的解密加密的话,那么在用户使用该框架的时候,就只能使用框架所规定的解密加密,这显然不具有灵活性。不同的用户解密、加密逻辑不同,所以这里的思路是在框架中只写抽象类,在DispatcherServlet中利用多态创建抽象类的继承类对象,调用继承类对象中的解密加密方法。而在web模块中用户可以自己重写一个解密加密抽象方法,这样调用的就是用户自定义的逻辑方法了。

抽象方法:

public abstract class HanderAdviserResolver {public abstract String beforeRequestBody(String reqData);public abstract String afterResponseBody(String respData);public String before(String reqData){return beforeRequestBody(reqData);}public String after(String respData){return afterResponseBody(respData);}
}

注意:这里的抽象方法是交给用户重写的,而自己的成员方法则直接调用抽象方法,通过这种方式实现在DispatcherServlet中调用用户重写方法的逻辑。

用户自定义前后置处理器:

public class BeforeAndAfterAdviser extends HanderAdviserResolver {@Overridepublic String beforeRequestBody(String reqData) {System.out.println("解密:"+reqData);return reqData;}@Overridepublic String afterResponseBody(String respData) {System.out.println("加密:"+respData);return respData;}
}

又一个问题来了,框架中怎样拿到用户自定义的类对象呢?思路和监听器拿到controller层类对象相似,通过配置文件拿到注解类,注解类通过一个注解注明用户自定义的前后置处理器路径。这样的注解叫做使能注解,它的功能就是使得DispatcherServlet能够拿到用户自定义类。然后在DispatcherServlet中重写init()方法,在方法中获取配置文件信息,进而拿到注解类,通过注解类的注解信息拿到自定义前后置处理器类对象,调用其重写的解密加密处理方法。

使能注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableAdviser {String adviserPackage();
}

注解类:

@EnableAdviser(adviserPackage= "com.qf.shop.web.adviser.BeforeAndAfterAdviser")

DispatcherServlet中:

private HanderAdviserResolver adviserResolver;public HanderAdviserResolver getAdviserResolver(String config){try {Class<?> clazz = Class.forName(config);EnableAdviser enableAdviserAnnotation = clazz.getAnnotation(EnableAdviser.class);String adviserPackage = enableAdviserAnnotation.adviserPackage();if(adviserPackage!=null){Class<?> adviserClass = Class.forName(adviserPackage);adviserResolver = (HanderAdviserResolver) adviserClass.newInstance();}return adviserResolver;} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}}@Overridepublic void init() throws ServletException {ServletContext servletContext = this.getServletContext();String config = servletContext.getInitParameter("config");adviserResolver = getAdviserResolver(config);}
//加密
if(methodDefinition.isAfterAdviserHasOrNot()){jsonString = adviserResolver.after(jsonString);
}
//解密
if(parameterDefinition.isBeforeAdviserHasOrNot()){jsonStr = adviserResolver.before(jsonStr);
}

异常

概念:DispatcherServlet中对于全局的异常需要进行处理,而具体如何处理也是由业务决定的,换言之是用户进行定义而非框架中写死。但是框架中又需要调用处理异常的方法,如何处理?-和前后置处理器一样,通过多态,servlet调用的是用户继承框架中抽象类的类重写的方法。

抽象类:

public abstract class HanderGlobalException {public abstract void handlerException(Exception err, HttpServletRequest request, HttpServletResponse response);public void hander(Exception err, HttpServletRequest request, HttpServletResponse response){handlerException(err,request,response);}
}

用户继承类:

public class GlobalException extends HanderGlobalException {@Overridepublic void handlerException(Exception err, HttpServletRequest request, HttpServletResponse response) {System.out.println("处理全局异常......");try {request.getRequestDispatcher("/err.jsp").forward(request,response);} catch (ServletException | IOException e) {throw new RuntimeException(e);}}
}

那么框架如何拿到用户自己写的处理异常类和方法呢?思路和前后置处理器一样,DispactherServlet在重写的init()方法中通过配置文件拿到配置类,配置类通过框架中写的使能注解将用户自定义的异常处理类路径告诉servlet。

使能注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableException {String exceptionPackage();
}

配置类:

/*** 当前项目的配置类
*/
@Configuration("com.qf.shop.web.controller")
@EnableAdviser(adviserPackage= "com.qf.shop.web.adviser.BeforeAndAfterAdviser")
@EnableException(exceptionPackage = "com.qf.shop.web.globalException.GlobalException")
public class AppConfig {
}

DispatcherServlet:

private HanderGlobalException globalException;public HanderGlobalException getGlobalException(String config){try {Class<?> clazz = Class.forName(config);EnableException enableExceptionAnnotation = clazz.getAnnotation(EnableException.class);String exceptionPackage = enableExceptionAnnotation.exceptionPackage();if(exceptionPackage!=null){Class<?> exceptionClass = Class.forName(exceptionPackage);globalException = (HanderGlobalException) exceptionClass.newInstance();}return globalException;} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}
}@Overridepublic void init() throws ServletException {ServletContext servletContext = this.getServletContext();String config = servletContext.getInitParameter("config");adviserResolver = getAdviserResolver(config);globalException = getGlobalException(config);}
try{//调用Controller层里的某个方法Object returnVal = method.invoke(t, args);if(returnVal!=null){//处理返回值handlerReturnVal(methodDefinition,returnVal,request,response,model);}
}catch (Exception e){globalException.hander(e,request,response);//处理全局异常
}

数据库模块

功能:该模块不是Spring-MVC中的一部分,是用来和数据库交互的框架。

思路:分包逐步实现JDBC。

base包:根据获得的结果集实现封装功能的接口,包含结果集处理器接口和行处理器接口

/**
* 结果集处理器的接口
*实现类:BeanHandler(获取单个对象)、BeanListHandler(获取对象集合)
*/
public interface ResultSetHandler<T> {public T handler(ResultSet resultSet)throws SQLException,IllegalAccessException,InstantiationException, InvocationTargetException;
}
/**
* 行处理器的接口
* @param <T>
*/
public interface RowProcessor<T> {public T toArray(ResultSet resultSet) throws SQLException;
}

handler包:实现接口:

/**
* 结果集处理接口的实现类
* 操作结果集并封装实体类
* @param <T>
*/
public class BeanHandler<T> implements ResultSetHandler<T> {private Class beanClass;public BeanHandler(Class beanClass) {this.beanClass = beanClass;}@Overridepublic T handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();if(resultSet.next()){T t = (T) beanClass.newInstance();for (int i = 0; i < columnCount; i++) {String columnName = metaData.getColumnName(i + 1);Object columnValue = resultSet.getObject(columnName);BeanUtils.copyProperty(t,columnName,columnValue);}return t;}return null;}
}
/**
* 结果集处理接口的实现类
* 处理结果集并封装为集合
* @param <T>
*/
public class BeanListHandler<T> implements ResultSetHandler<List<T>> {private Class beanListClass;public BeanListHandler(Class beanListClass) {this.beanListClass = beanListClass;}@Overridepublic List<T> handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {List<T> list = new ArrayList<>();ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();while(resultSet.next()){T t = (T) beanListClass.newInstance();for (int i = 0; i < columnCount; i++) {String columnName = metaData.getColumnName(i + 1);T columnValue = (T) resultSet.getObject(columnName);BeanUtils.copyProperty(t,columnName,columnValue);list.add(t);}}return list;}
}
/**
* 结果集处理接口的实现类
* 操作结果集并封装数组对象
*
* @param <T>
*/
public class ArrayHandler<T> implements ResultSetHandler<T> {//行处理器private RowProcessor<T> rowProcessor;public ArrayHandler(RowProcessor<T> rowProcessor) {this.rowProcessor = rowProcessor;}@Overridepublic T handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {return  rowProcessor.toArray(resultSet);}
}

其中,行处理的逻辑是拿到结果集后交给自定义的行处理接口实现类方法处理,这样做的目的是统一代码的格式(见后续web项目中的使用)。

processor包(处理行):

/**
* 行处理器的实现类,将结果集中的数据获取并封装成数组
* @param <T>
*/
public class BaskRowProcessor<T> implements RowProcessor<T[]> {private Class arrayTClass;public BaskRowProcessor(Class arrayTClass) {this.arrayTClass = arrayTClass;}@Overridepublic T[] toArray(ResultSet resultSet) throws SQLException {//创建数据容器List<T> list = new ArrayList<>();//遍历结果集while(resultSet.next()){T t = (T) resultSet.getObject(1);list.add(t);}if(list.size()==0||resultSet==null){throw new RuntimeException("参数异常无法获取泛型数组");}else {//创建数组T[] ts = (T[]) Array.newInstance(arrayTClass, list.size());//添加数据for (int i = 0; i < list.size(); i++) {ts[i] = list.get(i);}return ts;}}
}

通过结果集返回封装好的对象、列表、数组的功能已实现,接下类实现操作数据库返回结果集的功能:

core包:

public class QueryRunner {private DruidDataSource dataSource;private ThreadLocal<Connection> local = new ThreadLocal<>();public QueryRunner(DruidDataSource dataSource) {this.dataSource = dataSource;}//获取连接private Connection getConnection() throws SQLException {Connection connection = local.get();if(connection==null){connection = dataSource.getConnection();local.set(connection);}return connection;}//配置参数private PreparedStatement getPreparedStatement(Connection connection,String sql,Object... args) throws SQLException {PreparedStatement statement = connection.prepareStatement(sql);for (int i = 0; i < args.length; i++) {statement.setObject(i+1,args[i]);}return statement;}//更新操作public int update(String sql,Object... args) throws SQLException {Connection connection = getConnection();PreparedStatement statement = getPreparedStatement(connection, sql, args);int i = statement.executeUpdate();return i;}//查询操作public <T> T query(ResultSetHandler<T> handler,String sql,Object... args) throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {Connection connection = getConnection();PreparedStatement statement = getPreparedStatement(connection, sql, args);ResultSet resultSet = statement.executeQuery();T t = handler.handler(resultSet);return t;}}

至此,框架搭建完毕,接下来以用户创建的web项目举例:

数据库工具类:

public class JDBCUtils {//德鲁伊连接池引用private static DruidDataSource dataSource;static{//创建德鲁伊连接池dataSource = new DruidDataSource();//获取配置信息Properties properties = new Properties();try {properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("DBConfig.properties"));String username = properties.getProperty("username");String password = properties.getProperty("password");String url = properties.getProperty("url");String driverName = properties.getProperty("driverName");dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);dataSource.setDriverClassName(driverName);} catch (IOException e) {throw new RuntimeException(e);}}public static QueryRunner getQueryRunner(){return new QueryRunner(dataSource);}
}

controller层中对数据库进行操作:

//---操作数据库---------------------------------------------------------------------------------------
@RequestMapping("/test13.action")
public void test13() throws SQLException {//操作更新语句JDBCUtils.getQueryRunner().update("update user set password = ? where username = ?","123456","zs");
}
@RequestMapping("/test14.action")
public void test14() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询对象User zs = JDBCUtils.getQueryRunner().query(new BeanHandler<>(User.class), "select * from user where username = ?", "zs");System.out.println(zs);
}
@RequestMapping("/test15.action")
public void test15() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询对象列表List<User> users = JDBCUtils.getQueryRunner().query(new BeanListHandler<>(User.class), "select * from user");for (User user : users) {System.out.println("列表中的对象:"+user);}
}
@RequestMapping("/test16.action")
public void test16() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询所有的usernameString[] usernames = JDBCUtils.getQueryRunner().query(new ArrayHandler<>(new BaskRowProcessor<>(String.class)),"select username from user");System.out.println(usernames);
}

遇到的问题:

1.无法找到数据库连接的配置文件DBCfig.properties

解决方案:将配置文件放到src/main/resources文件夹中去,这样 资源文件才会被复制到target/classes 目录下。

相关文章:

手写Spring-MVC之前后置处理器与异常处理、数据库框架

Day48 手写Spring-MVC之前后置处理器与异常处理 前后置处理器 概念&#xff1a;从服务器获取的JSON数据可能是加密后的&#xff0c;因此服务端获取的时候需要进行解密&#xff08;前置处理器&#xff09;。 而从服务器传出的JSON数据可能需要加密&#xff0c;因此需要在处理返…...

学习笔记(linux高级编程)11

进程间通信 》信号通信 应用&#xff1a;异步通信。 中断&#xff0c;&#xff0c; 1~64&#xff1b;32应用编程。 如何响应&#xff1a; Term Default action is to terminate the process. Ign Default action is to ignore the signal. wait Core Default action is …...

vite+vue3+nginx配置统一公共前缀

方案1&#xff1a;重定向 server {listen 80;server_name localhost;location / {root /usr/share/nginx/html;index index.html;}location /music/ {proxy_pass http://127.0.0.1:80/;} }方案2&#xff1a;vitenginx双重配置 在方案1中&#xff0c;我们虽然能够实现 通过 …...

android 国内下载Gradle源

在中国使用 Gradle 时&#xff0c;可以配置使用一些国内的镜像源&#xff0c;以提高下载速度和稳定性。以下是几个常用的 Gradle 镜像源地址&#xff1a; 配置 gradle-wrapper.properties 文件: 阿里云: distributionUrlhttps\://services.gradle.org/distributions/gradle-7.…...

mysql8一键安装脚本(linux) 拿走即用

创建一个shell文件,将下面的代码放里面去,然后放到linux服务器上运行就可以了 #!/bin/bash#---------------------* # * # 2021-10-08 * # install mysql-8 * # * #---------------------*route=/usr #包存放路径 mys…...

C# 开发Winform DataGridView的增删改查实战

在C# WinForms应用程序中&#xff0c;DataGridView控件是一个非常强大的工具&#xff0c;用于显示和编辑表格数据。下面我将详细介绍如何在WinForm应用程序中使用DataGridView实现基本的数据库操作&#xff1a;增加、删除、修改和查询&#xff08;CRUD&#xff09;。 第一步&a…...

CentOS 7镜像列表服务下线,还想继续使用该怎么办?

目录 问题和解决方法 mirrorlist.centos.org 作用 vault.centos.org 作用 CentOS 7的生命周期已经在2024年6月30日终止&#xff08;End of Life&#xff0c;EOL&#xff09;&#xff0c;官方将不再对该版本进行问题修复、功能更新以及其他形式的维护支持。这意味着使用 Cent…...

代码随想录训练营第二十八天 122买卖股票的最佳时间II 55跳跃游戏 45跳跃游戏II 1005K次取反后最大化的数组和

第一题&#xff1a; 原题链接&#xff1a;122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 这题十分简单&#xff0c;就是把相邻天数的金额相减&#xff0c;如果发现大于0就加到res中&#xff0c;返回res即可 代码如下&#xff1a; …...

在node环境使用MySQL

什么是Sequelize? Sequelize是一个基于Promise的NodeJS ORM模块 什么是ORM? ORM(Object-Relational-Mapping)是对象关系映射 对象关系映射可以把JS中的类和对象&#xff0c;和数据库中的表和数据进行关系映射。映射之后我们就可以直接通过类和对象来操作数据表和数据了, 就…...

spdlog一个非常好用的C++日志库(四): 源码分析之logger类

目录 1.简介 2.类图关系 3.logger数据成员 4.logger函数成员 4.1.构造与析构 4.1.1.构造函数 4.1.2.拷贝构造、移动构造 4.2.交换操作 4.3.log()记录日志消息 4.3.1.格式串 4.3.2.普通字符串 4.3.3.日志级别 4.3.4.宽字符支持 4.4.sink_it_&#xff1a;将log消息…...

逻辑这回事(七)---- 器件基础

Xilinx FPGA创建了先进的硅模块(ASMBL)架构,以实现FPGA具有针对不同应用程序领域优化的各种功能组合的平台。通过这一创新,Xilinx提供了更多的设备选择,使客户能够为其特定设计选择具有正确的功能和功能组合的FPGA。ASMBL体系结构通过以下方式突破了传统的设计障碍:消除几…...

中俄汽车产业链合作前景广阔,东方经济论坛助力双边合作与创新

随着中国汽车零部件企业的竞争力和创新能力不断增强&#xff0c;中国汽车及零部件行业在俄罗斯的市场份额和品牌影响力显著提升&#xff0c;中俄两国在汽车产业链上的合作展现出巨大的潜力和广阔的前景。2024年5月&#xff0c;俄罗斯乘用车新车销量达到12.8万辆&#xff0c;同比…...

第六篇:精通Docker Compose:打造高效的多容器应用环境

精通Docker Compose&#xff1a;打造高效的多容器应用环境 1. 引言 1.1 目的与重要性 在现代软件开发中&#xff0c;随着应用程序的复杂性不断增加&#xff0c;传统的单一容器部署方式已无法满足需求。Docker Compose作为一种强大的工具&#xff0c;专门用于定义和运行多容器…...

C++视觉开发 一.OpenCV环境配置

一.OpenCV安装环境配置 1.OpenCV安装 &#xff08;1&#xff09;下载 官方下载链接&#xff1a;http://opencv.org/releases 这边选择需要的版本&#xff0c;我是在windows下的4.9.0。&#xff08;科学上网下载很快&#xff0c;否则可能会有点慢&#xff09; (2)安装 双击下…...

大数据面试题之Kafka(3)

目录 Kafka支持什么语义&#xff0c;怎么实现ExactlyOnce? Kafka的消费者和消费者组有什么区别?为什么需要消费者组? Kafka producer的写入数据过程? Kafka producer的ack设署 Kafka的ack机制&#xff0c;解决了什么问题? Kafka读取消息是推还是拉的模式?有什…...

视频监控平台web客户端的免密查看视频页:在PC浏览器上如何调试手机上的前端网页(PC上的手机浏览器的开发者工具)

目录 一、手机上做前端页面开发调试 1、背景 2、视频监控平台AS-V1000的视频分享页 3、调试手机前端页面代码的条件 二、手机端的准备工作 1、手机准备 2、手机的开发者模式 3、PC和手机的连接 &#xff08;1&#xff09;进入调试模式 &#xff08;2&#xff09;选择…...

力扣2488.统计中位数为 K 的子数组

力扣2488.统计中位数为 K 的子数组 等价转换 子数组为奇数 &#xff1a; 左小 右小 左大 右大 左小 – 左大 右大 – 右小 子数组为偶数 &#xff1a; 左小 右小 左大 右大 – 1 左小 – 左大 右大 – 右小 - 1提示中说明k为两数中左边那个 先从k的下标pos开始往左逆序…...

Zabbix对接Elasticsearch(ES)数据库(未成功)

0.需求分析 不管zabbix的后端数据库是oracle还是mysql&#xff0c;当zabbix监控的量级达到了一定程度后&#xff0c;那么对数据库的性能是一个非常严峻的挑战。特别是对历史数据的查询&#xff0c;将会变得非常非常的慢&#xff0c;别告诉我可以建索引优化&#xff0c;当量级达…...

【unity实战】使用Unity实现动作游戏的攻击 连击 轻重攻击和打击感

最终效果 文章目录 最终效果前言素材下载&#xff1a;玩家移动跳跃控制攻击动画配置轻攻击重攻击 攻击时禁止移动和攻击移动补偿敌人击退和播放受击动画受击特效攻击停顿和屏幕震动局部顿帧&#xff08;补充&#xff09;参考源码完结 前言 注意本文为自己的学习记录笔记&#…...

ELK 企业实战7

ELKkafkafilebeat企业内部日志分析系统 1、组件介绍 1、Elasticsearch&#xff1a; 是一个基于Lucene的搜索服务器。提供搜集、分析、存储数据三大功能。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口。Elasticsearch是用Java开发的&#xff…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...