在Java EE 7和WildFly中使用Bean验证来验证JAX-RS资源数据

我过去已经两次接触过这个主题。 首先,在我的文章《 在Java EE 6中将Bean验证与JAX-RS集成》中 ,介绍了甚至在Java EE平台规范中未定义之前,如何在JBoss AS 7中将Bean验证与JAX-RS结合使用的方法。 后来,在一篇为《 JAX Magazine 》撰写并随后发表在《 JAXenter 》上的文章中,使用了带有Glassfish 4服务器(第一台经过Java EE 7认证的服务器)的Java EE 7中定义的新标准方式。
现在,以前称为JBoss Application Server的WildFly 8终于达到了最终版本,并加入了Java EE 7认证的服务器俱乐部,现在该发表新文章了,重点介绍了这两个应用服务器GlassFish 4和WildFly之间的特性和差异。 8。

规格和API

Java EE 7是期待已久的Java EE 6的重大改进。随着Java EE的每个发行版,都添加了新功能并增强了现有规范。 Java EE 7以Java EE 6的成功为基础,并继续致力于提高开发人员的生产力。

JAX-RS是RESTful Web服务的Java API,是Java EE领域中发展最快的API之一。 当然,这是由于基于REST的Web服务的大量采用以及使用这些服务的应用程序数量的增加。

这篇文章将介绍配置REST端点以支持JavaScript客户端以及处理验证异常以将本地化错误消息发送给客户端(除了HTTP错误状态代码)所需的步骤。

源代码

本文随附的源代码可在GitHub上找到 。

Bean验证简介

JavaBeans Validation( Bean验证 )是一种新的验证模型,可作为Java EE 6平台的一部分使用。 约束条件支持Bean验证模型,该约束以注释的形式出现在JavaBeans组件(例如托管Bean)的字段,方法或类上。

javax.validation.constraints包中提供了一些内置约束。 Java EE 7教程包含具有所有这些约束的列表。

Bean验证中的约束通过Java注释表示:

public class Person {@NotNull@Size(min = 2, max = 50)private String name;// ...
}

Bean验证和RESTful Web服务

JAX-RS为提取请求值并将其绑定到Java字段,属性和参数(使用@HeaderParam@QueryParam等注释)提供了强大的支持。它还支持通过非注释参数将请求实体主体绑定到Java对象(即,未使用任何JAX-RS批注进行批注的参数)。 但是,在JAX-RS 2.0之前,必须以编程方式对资源类中的这些值进行任何其他验证。

最新版本的JAX-RS 2.0提供了一种解决方案,使验证批注可以与JAX-RS批注结合使用。
以下示例显示了如何使用@Pattern验证批注来验证路径参数:

@GET
@Path("{id}")
public Person getPerson(@PathParam("id")@Pattern(regexp = "[0-9]+", message = "The id must be a valid number")String id) {return persons.get(id);
}

除了验证单个字段外,您还可以使用@Valid批注验证整个实体。
例如,下面的方法接收一个Person对象并对其进行验证:

@POST
public Response validatePerson(@Valid Person person) {// ...
}

国际化

在前面的示例中,我们使用了默认或硬编码的错误消息,但这既是一种不好的做法,又一点也不灵活。 I18n是Bean验证规范的一部分,它使我们能够使用资源属性文件来指定自定义错误消息。 默认资源文件名称为ValidationMessages.properties并且必须包含属性/值对,例如:

person.id.notnull=The person id must not be null
person.id.pattern=The person id must be a valid number
person.name.size=The person name must be between {min} and {max} chars long

注意: {min}{max}是指与消息相关联的约束的属性。

一旦定义,这些消息就可以注入到验证约束中,例如:

@POST
@Path("create")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response createPerson(@FormParam("id")@NotNull(message = "{person.id.notnull}")@Pattern(regexp = "[0-9]+", message = "{person.id.pattern}")String id,@FormParam("name")@Size(min = 2, max = 50, message = "{person.name.size}")String name) {Person person = new Person();person.setId(Integer.valueOf(id));person.setName(name);persons.put(id, person);return Response.status(Response.Status.CREATED).entity(person).build();
}

要提供其他语言的翻译,必须使用翻译后的消息创建一个新文件ValidationMessages_XX.properties ,其中XX是所提供语言的代码。

不幸的是,对于某些应用程序服务器,默认的Validator提供程序不基于特定的HTTP请求支持i18n。 他们不考虑Accept-Language HTTP标头,并且始终使用Locale.getDefault()提供的默认Locale 。 为了能够使用Accept-Language HTTP标头(映射到浏览器选项中配置的语言)来更改Locale ,您必须提供一个自定义实现。

自定义验证器提供程序

尽管WildFly 8正确使用Accept-Language HTTP标头来选择正确的资源包,但其他服务器(例如GlassFish 4)却不使用此标头。 因此,为了完整性和与GlassFish代码的比较(在同一个GitHub项目下提供 ),我还为WildFly实现了自定义Validator提供程序。
如果要查看GlassFish示例,请访问JAXenter上的Bean验证与JAX-RS集成。

  1. 将RESTEasy依赖项添加到Maven
  2. WildFly使用RESTEasy ,这是JAX-RS规范的JBoss实现。
    验证程序提供程序和Exception Mapper必需具有RESTEasy依赖关系,本文稍后将对此进行讨论。 让我们将其添加到Maven:

    <dependencyManagement><dependencies><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-bom</artifactId><version>3.0.6.Final</version><scope>import</scope><type>pom</type></dependency></dependencies>
    </dependencyManagement><dependencies><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-jaxrs</artifactId><scope>provided</scope></dependency><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-validator-provider-11</artifactId><scope>provided</scope></dependency>
    </dependencies>

  3. 创建一个ThreadLocal来存储LocaleAccept-Language HTTP标头
  4. ThreadLocal变量与普通变量不同,每个访问线程的线程都有其自己的,独立初始化的变量副本。

    /*** {@link ThreadLocal} to store the Locale to be used in the message interpolator.*/
    public class LocaleThreadLocal {public static final ThreadLocal<Locale> THREAD_LOCAL = new ThreadLocal<Locale>();public static Locale get() {return (THREAD_LOCAL.get() == null) ? Locale.getDefault() : THREAD_LOCAL.get();}public static void set(Locale locale) {THREAD_LOCAL.set(locale);}public static void unset() {THREAD_LOCAL.remove();}
    }

  5. 创建一个请求过滤器以读取Accept-Language HTTP标头
  6. 请求过滤器负责读取客户端在Accept-Language HTTP标头中发送的第一语言并将Accept-Language Locale存储在我们的ThreadLocal

    /*** Checks whether the {@code Accept-Language} HTTP header exists and creates a {@link ThreadLocal} to store the* corresponding Locale.*/
    @Provider
    public class AcceptLanguageRequestFilter implements ContainerRequestFilter {@Contextprivate HttpHeaders headers;@Overridepublic void filter(ContainerRequestContext requestContext) throws IOException {if (!headers.getAcceptableLanguages().isEmpty()) {LocaleThreadLocal.set(headers.getAcceptableLanguages().get(0));}}
    }

  7. 创建自定义消息插值器以强制执行特定的Locale
  8. 接下来,创建一个自定义消息插值器,以通过绕过或覆盖默认的Locale策略来强制执行特定的Locale值:

    /*** Delegates to a MessageInterpolator implementation but enforces a given Locale.*/
    public class LocaleSpecificMessageInterpolator implements MessageInterpolator {private final MessageInterpolator defaultInterpolator;public LocaleSpecificMessageInterpolator(MessageInterpolator interpolator) {this.defaultInterpolator = interpolator;}@Overridepublic String interpolate(String message, Context context) {return defaultInterpolator.interpolate(message, context, LocaleThreadLocal.get());}@Overridepublic String interpolate(String message, Context context, Locale locale) {return defaultInterpolator.interpolate(message, context, locale);}
    }

  9. 配置验证器提供程序
  10. RESTEasy通过查找实现ContextResolver<GeneralValidator>的提供程序来获得Bean验证实现。
    要配置新的验证服务提供者以使用我们的自定义消息插值器,请添加以下内容:

    /*** Custom configuration of validation. This configuration can define custom:* <ul>* <li>MessageInterpolator - interpolates a given constraint violation message.</li>* <li>TraversableResolver - determines if a property can be accessed by the Bean Validation provider.</li>* <li>ConstraintValidatorFactory - instantiates a ConstraintValidator instance based off its class.* <li>ParameterNameProvider - provides names for method and constructor parameters.</li> ** </ul>*/
    @Provider
    public class ValidationConfigurationContextResolver implements ContextResolver<GeneralValidator> {/*** Get a context of type {@code GeneralValidator} that is applicable to the supplied type.** @param type the class of object for which a context is desired* @return a context for the supplied type or {@code null} if a context for the supplied type is not available from*         this provider.*/@Overridepublic GeneralValidator getContext(Class<?> type) {Configuration<?> config = Validation.byDefaultProvider().configure();BootstrapConfiguration bootstrapConfiguration = config.getBootstrapConfiguration();config.messageInterpolator(new LocaleSpecificMessageInterpolator(Validation.byDefaultProvider().configure().getDefaultMessageInterpolator()));return new GeneralValidatorImpl(config.buildValidatorFactory(),bootstrapConfiguration.isExecutableValidationEnabled(),bootstrapConfiguration.getDefaultValidatedExecutableTypes());}
    }

映射异常

默认情况下,当验证失败时,容器将引发异常,并将HTTP错误返回给客户端。

Bean验证规范定义了小的异常层次结构(它们都继承自ValidationException ),可以在验证引擎初始化期间或(在我们的情况下更重要)在输入/输出值验证期间抛出异常( ConstraintViolationException )。 如果抛出的异常是ValidationException的子类( ConstraintViolationException除外),则此异常将映射到状态码为500(内部服务器错误)的HTTP响应。 另一方面,当抛出ConstraintViolationException时,将返回两个不同的状态代码:

  • 500内部服务器错误)
    如果在验证方法返回类型时引发了异常。
  • 400(错误请求)
    除此以外。

不幸的是,WildFly并没有抛出ConstraintViolationException异常以获取无效的输入值, ResteasyViolationException抛出了ResteasyViolationException ,该异常实现了ValidationException接口。
可以自定义此行为,以允许我们将错误消息添加到返回给客户端的响应中:

/*** {@link ExceptionMapper} for {@link ValidationException}.* <p>* Send a {@link ViolationReport} in {@link Response} in addition to HTTP 400/500 status code. Supported media types* are: {@code application/json} / {@code application/xml} (if appropriate provider is registered on server).* </p>** @see org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper The original WildFly class:*      {@code org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper}*/
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {@Overridepublic Response toResponse(ValidationException exception) {if (exception instanceof ConstraintDefinitionException) {return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}if (exception instanceof ConstraintDeclarationException) {return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}if (exception instanceof GroupDefinitionException) {return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}if (exception instanceof ResteasyViolationException) {ResteasyViolationException resteasyViolationException = ResteasyViolationException.class.cast(exception);Exception e = resteasyViolationException.getException();if (e != null) {return buildResponse(unwrapException(e), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);} else if (resteasyViolationException.getReturnValueViolations().size() == 0) {return buildViolationReportResponse(resteasyViolationException, Status.BAD_REQUEST);} else {return buildViolationReportResponse(resteasyViolationException, Status.INTERNAL_SERVER_ERROR);}}return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}protected Response buildResponse(Object entity, String mediaType, Status status) {ResponseBuilder builder = Response.status(status).entity(entity);builder.type(MediaType.TEXT_PLAIN);builder.header(Validation.VALIDATION_HEADER, "true");return builder.build();}protected Response buildViolationReportResponse(ResteasyViolationException exception, Status status) {ResponseBuilder builder = Response.status(status);builder.header(Validation.VALIDATION_HEADER, "true");// Check standard media types.MediaType mediaType = getAcceptMediaType(exception.getAccept());if (mediaType != null) {builder.type(mediaType);builder.entity(new ViolationReport(exception));return builder.build();}// Default media type.builder.type(MediaType.TEXT_PLAIN);builder.entity(exception.toString());return builder.build();}protected String unwrapException(Throwable t) {StringBuffer sb = new StringBuffer();doUnwrapException(sb, t);return sb.toString();}private void doUnwrapException(StringBuffer sb, Throwable t) {if (t == null) {return;}sb.append(t.toString());if (t.getCause() != null && t != t.getCause()) {sb.append('[');doUnwrapException(sb, t.getCause());sb.append(']');}}private MediaType getAcceptMediaType(List<MediaType> accept) {Iterator<MediaType> it = accept.iterator();while (it.hasNext()) {MediaType mt = it.next();/** application/xml media type causes an exception:* org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response* object of type: org.jboss.resteasy.api.validation.ViolationReport of media type: application/xml*//*if (MediaType.APPLICATION_XML_TYPE.getType().equals(mt.getType())&& MediaType.APPLICATION_XML_TYPE.getSubtype().equals(mt.getSubtype())) {return MediaType.APPLICATION_XML_TYPE;}*/if (MediaType.APPLICATION_JSON_TYPE.getType().equals(mt.getType())&& MediaType.APPLICATION_JSON_TYPE.getSubtype().equals(mt.getSubtype())) {return MediaType.APPLICATION_JSON_TYPE;}}return null;}
}

上面的示例是ExceptionMapper接口的实现,该接口映射ValidationException类型的异常。 验证失败时,Validator实现将引发此异常。 如果该异常是ResteasyViolationException的实例, ResteasyViolationException除了HTTP 400/500状态代码外,我们ResteasyViolationException在响应中发送ViolationReport 。 这样可以确保客户端收到格式化的响应,而不仅仅是从资源传播的异常。

产生的输出类似于以下内容(JSON格式):

{"exception": null,"fieldViolations": [],"propertyViolations": [],"classViolations": [],"parameterViolations": [{"constraintType": "PARAMETER","path": "getPerson.id","message": "The id must be a valid number","value": "test"}],"returnValueViolations": []
}

运行和测试

要运行本文使用的应用程序,请使用Maven构建项目,将其部署到WildFly 8应用程序服务器中,然后将浏览器指向http:// localhost:8080 / jaxrs-beanvalidation-javaee7 / 。

另外,您也可以运行在类中的测试PersonsIT其内置的Arquillian和JUnit的 。 Arquillian将自动启动嵌入式WildFly 8容器,因此请确保您没有在同一端口上运行其他服务器。

建议和改进

  1. 为了实现自定义的验证程序提供程序,我们依赖于应用程序服务器代码。 在GlassFish 4上,需要实现ContextResolver ContextResolver<ValidationConfig> ,而在WildFly 8上,我们需要实现ContextResolver<GeneralValidator> 。 为什么不在Java EE 7规范中定义ValidationConfigGeneralValidator必须实现的接口,而不是依赖于应用程序服务器特定的代码?
  2. 使WildFly 8 Embedded易于使用和通过Maven进行配置。 当前,要使Arquillian可以使用它,需要下载WildFly发行版(org.wildfly:wildfly-dist),将其解压缩到target文件夹中,并在Surefire / Failsafe Maven插件上配置系统属性:
    <systemPropertyVariables><java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager><jboss.home>${wildfly.home}</jboss.home><module.path>${wildfly.home}/modules</module.path>
    </systemPropertyVariables>

    而对于Glassfish,您只需要定义正确的依赖项(org.glassfish.main.extras:glassfish-embedded-all)。

  3. 使RESTEasy成为WildFly Embedded的可传递依赖项。 仅通过定义provided WildFly Embedded依赖项,在编译时就可以使用所有WildFly模块,这将是一个很好的生产力提升。
  4. 当前无法在Eclipse上使用选项Run As >> JUnit Test ,因为必须存在名为jbossHome的系统属性。 Eclipse不会从Surefire / Failsafe配置中读取此属性。 有没有解决方法?
  5. 当使用RESTEasy的ExceptionMapper<ValidationException>默认实现时,以application/xml媒体类型请求数据并发生验证错误,将引发以下异常:
    org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure:Could not find MessageBodyWriter for response object of type:org.jboss.resteasy.api.validation.ViolationReport of media type:application/xml

    这是RESTEasy错误吗?

翻译自: https://www.javacodegeeks.com/2014/04/validating-jax-rs-resource-data-with-bean-validation-in-java-ee-7-and-wildfly.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/364233.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EventUtil.addHandler方法

EventUtil.addHandler&#xff1a;addHandler 方法&#xff0c;职责是分别视情况而定来使用DOM0级方法、DOM2级方法或IE方法来添加事件。 这个方法属于一个名字叫EventUtil的对象&#xff0c;可以使用这个对象来处理浏览器间的差异。     addHandler() 方法…

Linux scp 指令

scp指令可从远程服务器下载文件或上传文件至远程服务器(适用于: mac没安装ftp软件临时使用) 上传: scp 本地文件路径 rootIP:远程路径 例: scp /Users/xxx/Downloads/email.png rootxxx.xxx.xxx.xx:/root/img 下载:scp rootxxx.xxx.xxx.xx:/root/img/email.png /Users/xxx/Down…

mysql游标遍历数据库_MySQL数据库中,使用游标循环遍历_MySQL

/*对*dt库下的所有数据表删除docuemttype为空和documenttype为MD,PD,ET的数据&#xff1a;delete from 表名 where length(documenttype)<2 or documenttype is null or documenttype in (et,md,pd);*/DELIMITER $$USE 数据库名称1$$DROP PROCEDURE IF EXISTS 存储过程名称1…

RN启动报错,环境相关问题

启动RN的时候刚开始报错&#xff1a; The request was denied by service delegate (SBMainWorkspace) for reason: Security ("Entitlement "com.apple.frontboard.debugapplications" required to launch applications for debugging"). 查询网络上的解决…

在Spring MVC Web应用程序中添加社交登录:集成测试

我已经写了关于为使用Spring Social 1.1.0的应用程序编写单元测试的挑战&#xff0c;并为此提供了一种解决方案 。 尽管单元测试很有价值&#xff0c;但是它并不能真正告诉我们我们的应用程序是否正常运行。 这就是为什么我们必须为此编写集成测试的原因 。 这篇博客文章可以…

js基本包装类型和引用类型

回顾 1.什么是基本类型&#xff1f; 共5个。boolean,string,number,null,undefined. 2.什么是引用类型&#xff1f; 引用类型的值是对象&#xff0c;保存在堆内存中&#xff1b; 引用类型的变量实际上是一个指针&#xff0c;它保存在栈中&#xff0c;指向堆内存中的对象&am…

mysql数据库套件_MySQL数据库管理开发套件(EMS SQL Management Studio For MySQL)下载 v1.3.0.46170 官方版 - 比克尔下载...

EMS SQL Management Studio For MySQL是一个强大的MySQL数据库管理和开发套件&#xff0c;由很多工具组成&#xff0c;涉及MySQL数据库管理、导入、导出、迁移、测试、备份、比较、同步等数据库管理和开发中需要的绝大部分功能&#xff0c;为开发人员提供了一个MySQL数据库管理…

异步和多线程

异步是目的&#xff0c;而多线程是实现这个目的的方法。 异步&#xff08;目的&#xff09;&#xff1a;由系统决定何时&#xff0c;如何去执行&#xff0c;非阻塞的&#xff0c;执行后回调。 CPU可以从线程池中获取一个线程资源&#xff0c;执行操作&#xff0c;并在执行完成后…

vue.config.js配置别名alias、配置生产环境清除console

项目中使用引入文件有时候路径比较深&#xff0c;需要使用"../../../xx.js"这种类似的路劲引入&#xff0c;这种方式比较笨&#xff0c;可以使用webpack的别名alias配置来解决。 首先&#xff0c;先确定项目中是否有path模块&#xff1a; 如果没有path模块需要先安装…

借助Java 8和lambdas,可以一起使用AssertJ和Awaitility

AssertJ和Awaitility是在自动代码测试中使用的两个我最喜欢的工具。 不幸的是直到最近&#xff0c;还不能一起使用它。 但是随后Java 8进入了游戏&#xff0c;几十行代码足以使其在Awaility 1.6.0中实现。 AssertJ提供了一组丰富的断言&#xff0c;其中包含非常有用的错误消息…

小程序-冒泡事件

小程序冒泡事件与非冒泡事件 会随之触发父元素的称为冒泡事件&#xff0c;反之&#xff0c;则是非冒泡事件 wxml&#xff1a; <view class"view1" bindtap"view1click"> <!-- 用 bind 绑定事件 -->view1<view class"view2" bin…

ruhr启动mysql数据库_Mysql表类型(存储引擎)的选择

以下内容转载自&#xff1a;https://www.cnblogs.com/jswang/p/6923911.html7.1 mysql存储引擎概述插件式存储引擎是mysql数据库最重要的特性之一&#xff0c;用户可以根据应用的需要选择ruhr存储和索引数据&#xff0c;是否使用事务等。InnoDB和BDB提供事务安全表&#xff0c;…

【JOURNAL】好久了啊

40天没有blog了&#xff0c;史无前例。项目、个人、朋友等等事情都同时很多&#xff0c;一根蜡烛3头点。这个星期还参加了一个5天的封闭workshop&#xff0c;加上这个酒店上网还贵得疯狂&#xff0d;&#xff0d;1块钱1分钟&#xff0d;&#xff0d;是的&#xff0c;你没有看错…

01 辅助函数之加密函数

常用的加密算法 常见的对称加密算法有 AES、DES、3DES 和 Itsdangerous &#xff0c;md5 &#xff0c;base64 Itsdangerous 加密和解密方法 2 from itsdangerous import TimedJSONWebSignatureSerializer as serializer3 class ItsdangerouSecret:4 # 初始化5 def __in…

mysql导出表结构 创建_mysql如何导出表结构为文本文件

Log Goup ID&#xff0c;可能会配置多个redo组&#xff0c;每个组对应一个id&#xff0c;当前都是0&#xff0c;占用4字节Start LSN&#xff0c;这个redo log文件开始日志的lsn&#xff0c;占用8字节Log File Number&#xff0c;总是为0&#xff0c;占用4字节Created By&#x…

SB错误集合

1. 没看数据是不是从0开始&#xff0c;导致treeDP时以0为父亲跑1&#xff08;战略游戏&#xff09; 2.建树&#xff0c;无向图&#xff0c;边数组大小用MAXN却忘了 * 2 ( 战略游戏 ) 3.treeDP用 转移状态 却用了导致只统计了一个儿子&#xff08;战略游戏&#xff09;转载于:…

uoj#213. 【UNR #1】争夺圣杯(单调栈)

传送门 我们枚举每一个元素&#xff0c;用单调栈做两遍计算出它左边第一个大于它的位置\(l[i]\)和右边第一个大于它的位置\(r[i]\)&#xff0c;那么一个区间以它为最大值就意味着这个区间的左端点在\([l[i]1,i]\)之间&#xff0c;右端点在\([i,r[i]-1]\)之间 设\(xi-l[i],yr[i]…

Java 8功能接口–实现Scala类型的随机思维

在“ 使用Scala进行功能编程”课程的一项作业中&#xff0c;引入了一种称为Terrain的类型-Terrain表示一个区域&#xff0c;该区域的某些部分可以访问&#xff0c;而某些部分则不能访问。 因此&#xff0c;以一种非常聪明的方式在任务中以下列方式定义了Terrain&#xff1a; c…

iOS中常见的内存泄漏,及避免泄漏的最佳方案

引言 在iOS应用开发中&#xff0c;内存泄漏是一个常见而严重的问题。本文将探讨一些iOS应用中常见的内存泄漏原因&#xff0c;并提供一些最佳实践&#xff0c;帮助开发者避免这些问题&#xff0c;提高应用性能。 什么是内存泄漏 内存泄漏是指在程序运行时&#xff0c;由于错…

bootstrap-table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)

1.bootstrap-table 单击单行选中 $(#gzrwTable).on(click-row.bs.table, function(e, row, $element) { $(.success).removeClass(success);// 清除前一次操作已选中行的选中状态 $($element).addClass(success);// 选中行添加选中状态 });2.bootstrap-table 获取选中行信息 fu…