前言

上一章节,讲解了如何使用Spring-WS构建WebService服务。其实,创建WebService的方式有很多的,今天来看看如何使用apache cxf来构建及调用WebService服务。

一点知识

何为Apache-CXF

Apache CXF是一个开源的Services框架,CXF帮助您利用Frontend编程 API 来构建和开发Services,像JAX-WSJAX-RS。这些Services可以支持多种协议,比如:SOAPXML/HTTPRESTful HTTP 或者CORBA,并且可以在多种传输协议上运行,比如:HTTPJMS 或者JBICXF大大简化了 Services 的创建,同时它可以天然地和Spring进行无缝集成

以下是官网给出的介绍:https://github.com/apache/cxf

最常用的是使用cxf开发web-service。本身是基于JAX-WS规范来实现的。当然,本身CXF也实现了JAX-RS规范来实现RESTFul Service

关于JAX-WS规范

JAX-WS全称:Java API for XML-Based Web ServicesJAX-WS是一种编程模型,它通过支持将基于注释的标准模型用于开发Web Service应用程序和客户机来简化应用程序开发。

JAX-WS是Java程序设计语言一个用来创建Web服务的API。

  • 在服务器端,用户只需要通过Java语言定义远程调用所需要实现的接口SEI(service endpoint interface),并提供相关的实现,通过调用JAX-WS的服务发布接口就可以将其发布为WebService接口。
  • 在客户端,用户可以通过JAX-WS的API创建一个代理(用本地对象来替代远程的服务)来实现对于远程服务器端的调用。当然JAX-WS也提供了一组针对底层消息进行操作的API调用,你可以通过Dispatch直接使用SOAP消息或XML消息发送请求或者使用Provider处理SOAP或XML消息。

常用注解介绍

JAX-WS提供了一系列的注解,可以对WebService的接口规范化。以下介绍下最常用的几个注解。

  • @WebService:用于将Java类标记为实现Web Service或者将服务端点接口 (SEI) 标记为实现Web Service接口。

    其包含的属性有:

    • name:此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType 的名称。缺省值为 Java 类的简单名称 + Service。(字符串)
    • targetNamespace:默认的值为 "http://包名/" ,可以通过此变量指定一个自定义的targetNamespace值。
    • serviceName:对外发布的服务名,指定Web Service的服务名称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串)
    • endpointInterface:
    • portName:wsdl:portName的值。缺省值为WebService.name+Port
    • wsdlLocation:指定用于定义 Web Service 的 WSDL 文档的 Web 地址
  • @WebMethod:表示作为一项Web Service操作的方法。仅支持在使用@WebService 注解的类上使用@WebMethod注解。

    • operationName:指定与此方法相匹配的wsdl:operation 的名称。缺省值为 Java 方法的名称。(字符串)
    • action:定义此操作的行为。对于 SOAP 绑定,此值将确定 SOAPAction 头的值。缺省值为 Java 方法的名称。(字符串)
    • exclude:指定是否从 Web Service 中排除某一方法。缺省值为 false。(布尔值)
  • @WebParam:用于定制从单个参数至Web Service消息部件和XML元素的映射。

其他注解,可以查看:WebService注解总结

为了有个直观感受,大家可以看看以下这个wsdl文件,对应以上各注解属性的值(加了前缀oKong)。

//@WebService 属性示例
@WebService(targetNamespace = 'http://www.lqdev.cn/webservice' ,name = "oKongName", serviceName="oKongServiceName", portName = "oKongPortName",endpointInterface="cn.lqdev.learning.springboot.cxf.service.AuthorService") //@webMethod @WebParam 常用属性示例
@WebMethod(operationName="oKongOperationName",action="oKongAction")
String getAuthorName(@WebParam(name = "paramName") String name);

标记的有点花,⊙﹏⊙‖∣。大家可以自己对照下。

SpringBoot整合CXF实例

接下来,我们以一个简单的示例来演示下,如何发布服务及如何进行服务调用。

服务端构建

创建一个工程:spring-boot-cxf-service.

0.引入CXF的POM文件

        <dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.2.5</version>
</dependency>

1.创建实体,按JAX-WS规范,创建接口及其实现类。

AuthorDto.java

/**
* 作者信息实体
* @author oKong
*
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthorDto { String name; List<String> hobby; String birthday; String description; Sex sex;
}

Sex.java性别枚举类

/**
* 性别枚举类
* @author oKong
*
*/
public enum Sex { MALE("male"),
FEMALE("female"); String value; Sex(String value) {
this.value = value;
} public String value() {
return value;
} public static Sex fromValue(String v) {
for (Sex c : Sex.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}

AuthorService.java接口类

/**
* 创建服务接口
* @author oKong
*
*/
@WebService(targetNamespace = WsConst.NAMESPACE_URI ,name = "authorPortType")
public interface AuthorService { /**
* 根据名称获取作者信息
* @author 作者:oKong
*/
@WebMethod(operationName="getAuthorByName")
AuthorDto getAuthor(@WebParam(name = "authorName") String name); /**
* 获取作者列表信息
* @author oKong
*/
@WebMethod
List<AuthorDto> getAuthorList(); /**
* 返回字符串测试
* @author oKong
*/
String getAuthorString(@WebParam(name = "authorName")String name);
}

AuthorServiceImpl.java接口实现类

@WebService(
targetNamespace = WsConst.NAMESPACE_URI, //wsdl命名空间
name = "authorPortType", //portType名称 客户端生成代码时 为接口名称
serviceName = "authorService", //服务name名称
portName = "authorPortName", //port名称
endpointInterface = "cn.lqdev.learning.springboot.cxf.service.AuthorService")//指定发布webservcie的接口类,此类也需要接入@WebService注解
public class AuthorServiceImpl implements AuthorService{ @Override
public AuthorDto getAuthor(String name) {
AuthorDto author = new AuthorDto();
author.setBirthday("1990-01-23");
author.setName("姓名:" + name);
author.setSex(Sex.MALE);
author.setHobby(Arrays.asList("电影","旅游"));
author.setDescription("描述:一枚趔趄的猿。现在时间:" + new Date().getTime());
return author;
} @Override
public List<AuthorDto> getAuthorList() {
List<AuthorDto> resultList = new ArrayList<>();
AuthorDto author = new AuthorDto();
author.setBirthday("1990-01-23");
author.setName("姓名:oKong");
author.setSex(Sex.MALE);
author.setHobby(Arrays.asList("电影","旅游"));
author.setDescription("描述:一枚趔趄的猿。现在时间:" + new Date().getTime());
resultList.add(author);
resultList.add(author);
return resultList;
} @Override
public String getAuthorString(String name) {
AuthorDto author = getAuthor(name);
return author.toString();
}
}

注意:相关注解可以查看章节:常用注解介绍

主要是接口实现类的@WebService对应属性值都要wsdl文件的映射关系。

@WebService(
targetNamespace = WsConst.NAMESPACE_URI, //wsdl命名空间
name = "authorPortType", //portType名称 客户端生成代码时 为接口名称
serviceName = "authorService", //服务name名称
portName = "authorPortName", //port名称
endpointInterface = "cn.lqdev.learning.springboot.cxf.service.AuthorService")//指定发布webservcie的接口类,此类也需要接入@WebService注解

2.创建常量类,配置类,设置访问uri路径等。

WsConst.java

/**
* 常量类
* @author oKong
*
*/
public class WsConst {
public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice";
}

CxfWebServiceConfig.java

/**
* cxf配置类
* @author oKong
*
*/
@Configuration
public class CxfWebServiceConfig { //这里需要注意 由于springmvc 的核心类 为DispatcherServlet
//此处若不重命名此bean的话 原本的mvc就被覆盖了。可查看配置类:DispatcherServletAutoConfiguration
//一种方法是修改方法名称 或者指定bean名称
//这里需要注意 若beanName命名不是 cxfServletRegistration 时,会创建两个CXFServlet的。
//具体可查看下自动配置类:Declaration org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration
//也可以不设置此bean 直接通过配置项 cxf.path 来修改访问路径的
@Bean("cxfServletRegistration")
public ServletRegistrationBean dispatcherServlet() {
//注册servlet 拦截/ws 开头的请求 不设置 默认为:/services/*
return new ServletRegistrationBean(new CXFServlet(), "/ws/*");
} /**
* 申明业务处理类 当然也可以直接 在实现类上标注 @Service
* @author oKong
*/
@Bean
public AuthorService authorService() {
return new AuthorServiceImpl();
} /*
* 非必要项
*/
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
SpringBus springBus = new SpringBus();
return springBus;
} /*
* 发布endpoint
*/
@Bean
public Endpoint endpoint(AuthorService authorService) {
EndpointImpl endpoint = new EndpointImpl(springBus(), authorService);
endpoint.publish("/author");//发布地址
return endpoint;
}
}

注意事项:

  • 配置ServletRegistrationBean时,需要注意设置方法的名称或者bean的名称时,不要和默认的DispatcherServlet类重名了,会导致原先的mvc接口无法使用,因为被覆盖了。
  • 修改访问的路径可以通过设置ServletRegistrationBean来修改,但同时,要注意需要设置bean的名称为cxfServletRegistration,不然会造成注册多个CXFServlet的。具体原因可查看自动配置类:org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration

所以,修改访问路径还可以通过配置项:cxf.path来设置。其默认的访问url为:/services

3.创建启动类,同时启动应用。

/**
* cxf服务发布示例
* @author oKong
*
*/
@SpringBootApplication
@Slf4j
public class CxfServiceApplication { public static void main(String[] args) throws Exception {
SpringApplication.run(CxfServiceApplication.class, args);
log.info("spirng-boot-cxf-service-chapter34启动!");
}
}

启动后,可以从控制台看见可以访问的url路径信息。

2018-11-10 22:06:40.898  INFO 46364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'CXFServlet' to [/ws/*]
2018-11-10 22:06:40.899 INFO 46364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]

访问:http://127.0.0.1:8080/ws/author?wsdl ,验证是否发布成功。

自此,webService发布成功了。

客户端调用

创建一个客户端工程:spring-boot-cxf-client

0.引入cxf依赖。

        <dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.2.5</version>
</dependency>

1.创建wsdl文件,同时利用插件:cxf-codegen-plugin创建相关类。

<!-- cxf-codegen-plugin -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>3.2.5</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>src/main/resources/wsdl/author.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/author.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>

wsdl文件,放入main/resources/wsdl目录下。之后执行:mvn generate-sources命令,就会自动创建相应的类文件了。拷贝相应的类文件至src/java目录下即可。或者直接指定sourceRoot也是可以的。

2.创建调用的配置类,这里演示两种方式。

WsConst.java

/**
* 常量类
* @author oKong
*
*/
public class WsConst {
public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice";
public static final String SERVICE_ADDRESS= "http://127.0.0.1:8080/ws/author?wsdl";
}

CxfClinetConfig.java

/**
* 配置类
*
* @author oKong
*
*/
@Configuration
public class CxfClientConfig { /**
* 以接口代理方式进行调用 AuthorPortType接口
*/
@Bean("cxfProxy")
public AuthorPortType createAuthorPortTypeProxy() {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(AuthorPortType.class);
jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);//服务地址:http://127.0.0.1:8080/ws/autho return (AuthorPortType) jaxWsProxyFactoryBean.create();
} /*
* 采用动态工厂方式 不需要指定服务接口
*/
@Bean
public Client createDynamicClient() {
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient(WsConst.SERVICE_ADDRESS);
return client;
}
}

注意:除了使用JaxWsProxyFactoryBeanJaxWsDynamicClientFactory调用外,还可以直接使用自动生成的AuthorService类直接调用的,此类继承至javax.xml.ws.Service

如:

    /*
* 直接调用
*/
@Bean("jdkProxy")
public AuthorPortType createJdkService() {
AuthorService authorService = new AuthorService();
return authorService.getAuthorPortName();
}

其实,最后都是使用AuthorPortType进行调用的。

3.创建控制层,进行调用示例。

/**
* 调用示例
* @author oKong
*
*/
@RestController
@RequestMapping("/cxf")
public class DemoController { @Autowired
Client client; @Autowired
@Qualifier("cxfProxy")
AuthorPortType authorPort; @GetMapping("/getauthorstring")
public String getAuthorString(String authorName) {
return authorPort.getAuthorString(authorName);
} @GetMapping("/getauthor")
public AuthorDto getAuthor(String authorName) {
return authorPort.getAuthorByName(authorName);
} @GetMapping("/getauthorlist")
public List<AuthorDto> getAuthorList() {
return authorPort.getAuthorList();
} @GetMapping("/dynamic/{operation}")
public Object getAuthorStringByDynamic(@PathVariable("operation")String operationName, String authorName) throws Exception {
//这里就简单的判断了
Object[] objects = null;
// client.getEndpoint().getBinding().getBindingInfo().getOperations()
if ("getAuthorList".equalsIgnoreCase(operationName)) {
objects = client.invoke(operationName);
} else if ("getAuthorString".equalsIgnoreCase(operationName)) {
objects = client.invoke(operationName, authorName);
} else if ("getAuthorByName".equalsIgnoreCase(operationName)) {
objects = client.invoke(operationName, authorName);
} else {
throw new RuntimeException("无效的调用方法");
}
return objects != null && objects.length > 0 ? objects[0] : "返回异常";
}
}

4.编写启动类,同时制定应用端口为:8090。

/**
* cxf-客户端调用示例
*
* @author oKong
*
*/
@SpringBootApplication
@Slf4j
public class CxfClientApplication { public static void main(String[] args) throws Exception {
SpringApplication.run(CxfClientApplication.class, args);
log.info("spring-boot-cxf-client-chapter34启动!");
}
}

端口号配置:

server.port=8090

5.启动应用,依次访问。查看是否调用成功。

http://127.0.0.1:8090/cxf/getauthorstring?authorName=oKong

http://127.0.0.1:8090/cxf//getauthorlist?authorName=oKong

动态工厂方式调用:

http://127.0.0.1:8090/cxf/dynamic/getAuthorList?authorName=oKong

其他的就不一一贴图了,可自行访问下。

异常捕获

Cxf发生异常时,会统一抛出:org.apache.cxf.interceptor.Fault类的,所以想要捕获异常,可以在统一异常里面进行捕获,关于统一异常处理,可以查看文章:第八章:统一异常、数据校验处理

自定义拦截器

CXF的拦截器分为两种:InInterceptorOutInterceptor。显而易见,InInterceptor可以处理soap请求消息OutInterceptor可以处理soap响应消息。其拦截器都继承至AbstractPhaseInterceptor<Message>接口类,而且,本身也自带了很多的拦截器,可以自行添加看看,比如日志拦截器之类的:LoggingInInterceptorLoggingOutInterceptor

请求流程图:

拦截器链的阶段:

输入拦截器链有如下几个阶段,这些阶段按照在拦截器链中的先后顺序排列。

输出拦截器链有如下几个阶段,这些阶段按照在拦截器链中的先后顺序排列。

具体名称,可查看:org.apache.cxf.phase.Phase

现在,我们自定义个实现拦截器,实现请求时header需要带上特定参数,或者大家可不写一些安全校验的自定义拦截器,本例只是简单的示例。

服务端拦截器

1.检验拦截器:CheckAuthInterceptor.java

/**
* 简易-安全校验拦截器
*
* @author oKong
*
*/
@Slf4j
public class CheckAuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public CheckAuthInterceptor() {
super(Phase.PRE_INVOKE);// 拦截节点:调用之前
} @Override
public void handleMessage(SoapMessage message) throws Fault {
log.info("检验拦截器开始检验:{}", message);
// 处理方法
List<Header> headers = message.getHeaders(); // 判断是否存header
// 检查headers是否存在
if (headers == null | headers.size() < 1) {
throw new Fault(new IllegalArgumentException("验证失败,请传入正确参数(40001)"));//可自定义编码规范
}
//取出header
Header header = headers.get(0);
//获取对象
Element element = (Element) header.getObject();//这里获取的就时 auth对象了
NodeList tokenNode = element.getElementsByTagName("token");
if(tokenNode == null || tokenNode.getLength() < 1) {
//无token节点
throw new Fault(new IllegalArgumentException("验证失败,请传入正确参数(40002)"));//自定义编码规范
}
//获取token
String token = tokenNode.item(0).getTextContent();
log.info("请求的token为:{}", token);
//这里可以对token 有效性进行判断
}
}

2.Endpoint中加入拦截器配置。

    /*
* 发布endpoint
*/
@Bean
public Endpoint endpoint(AuthorService authorService) {
EndpointImpl endpoint = new EndpointImpl(springBus(), authorService);
endpoint.publish("/author");//发布地址
endpoint.getInInterceptors().add(createCheckAuthInterceptor());//加入拦截器
// endpoint.getOutInterceptors().add()//响应拦截器
return endpoint;
} @Bean
public Interceptor<SoapMessage> createCheckAuthInterceptor(){
return new CheckAuthInterceptor();
}

客户端拦截器

1.编写拦截器。

/**
* 简易-安全校验拦截器
* @author oKong
*
*/
public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage>{ public AuthInterceptor() {
super(Phase.PREPARE_SEND);//准备请求时进行拦截
} @Override
public void handleMessage(SoapMessage message) throws Fault {
//处理方法
List<Header> headers = message.getHeaders(); Document doc = DOMUtils.createDocument();
Element element = doc.createElement("auth");
Element tokenEle = doc.createElement("token");
tokenEle.setTextContent(UUID.randomUUID().toString());
element.appendChild(tokenEle);
//这里需要注意 默认情况下 是使用 org.w3c.dom.Element对象设置对象值的。
//也可以指定 DataBinding 设置对象的。可继承抽象类: org.apache.cxf.databinding.AbstractDataBinding
//具体源码可查看:org.apache.cxf.binding.soap.interceptor.SoapOutInterceptor
Header tokenHeader = new SoapHeader(new QName(""), element);
// tokenHeader.setDataBinding()
headers.add(tokenHeader);
} }

这里需要注意:

  • 设置header时,默认是org.w3c.dom.Element对象。
  • 自定义对象时,可设置DataBinding类来解析(未尝试,只是看了一眼源码,里面有此逻辑,有兴趣的同学可以自行试试)。

2.请求类中加入拦截器。

CxfClientConfig.java

    /**
* 以接口代理方式进行调用 AuthorPortType接口
*/
@Bean("cxfProxy")
public AuthorPortType createAuthorPortTypeProxy() {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(AuthorPortType.class);
jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);//服务地址:http://127.0.0.1:8080/ws/autho
jaxWsProxyFactoryBean.getOutInterceptors().add(createInterceptor());//加入自定义拦截器
return (AuthorPortType) jaxWsProxyFactoryBean.create();
} /*
* 采用动态工厂方式 不需要指定服务接口
*/
@Bean
public Client createDynamicClient() {
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient(WsConst.SERVICE_ADDRESS);
client.getOutInterceptors().add(createInterceptor());
return client;
} @Bean
public Interceptor<SoapMessage> createInterceptor() {
return new AuthInterceptor();
}

重新启动后,再次请求就可以看见相关日志输出了,可以试着不设置token,看看有拦截。

异常请求:

参考资料

  1. http://cxf.apache.org/docs/springboot.html

  2. https://www.code996.cn/post/2017/cxf-interceptor/

总结

本章节主要简单介绍了apache-cxf的使用。这文章示例写下来,我发现比spring-ws更简单呀,也更让人容易理解、逻辑比较清晰,而且也能设置一些差异化的东西。不知道是不是真的对spring-ws了解的不够呀,没有发现spring-ws的优点呀。自此,关于WebService的文章就暂时告一段落了。

最后

目前互联网上很多大佬都有SpringBoot系列教程,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。

老生常谈

  • 个人QQ:499452441
  • 微信公众号:lqdevOps

个人博客:http://blog.lqdev.cn

完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-34

原文地址:http://blog.lqdev.cn/2018/11/12/springboot/chapter-thirty-four/

SpringBoot | 第三十四章:CXF构建WebService服务的更多相关文章

  1. Gradle 1.12用户指南翻译——第三十四章. JaCoCo 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  2. “全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. “全栈2019”Java第三十四章:可变参数列表

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. Python 3标准库 第十四章 应用构建模块

    Python 3标准库 The Python3 Standard Library by  Example -----------------------------------------第十四章   ...

  5. SpringBoot系列(十四)集成邮件发送服务及邮件发送的几种方式

    往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件详解 SpringBoot系列(四)web静 ...

  6. 第三十四章 metrics(2)- 搭建metrics平台

    一.基本架构图 1.整个架构组件: java客户端 dropwizard-metrics:springboot.hystrix的metric体层使用了这个,所以我们需要自己封装向statsd发包的方法 ...

  7. SpringBoot | 第三十五章:Mybatis的集成和使用

    前言 最近收到公众号留言说,单纯的Mybatis的集成和使用.前面在第九章:Mybatis-plus的集成和使用介绍了基于mybatis-plus的集成和使用.后者也只是对mybatis进行了功能增强 ...

  8. SpringBoot | 第三十二章:事件的发布和监听

    前言 今天去官网查看spring boot资料时,在特性中看见了系统的事件及监听章节.想想,spring的事件应该是在3.x版本就发布的功能了,并越来越完善,其为bean和bean之间的消息通信提供了 ...

  9. 我的学习之路_第三十四章_jsp

    jsp 在只有servlet时,输出页面内容比较麻烦(成本高,java代码中输出HTML标签),所以需要一种技术,主要是HTML页面的代码(HTML,css,js),可以嵌入java代码,来实现动态页 ...

随机推荐

  1. go语言 defer 你不知道的秘密!

    go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱哦.我们先来看几个例子. 例一: defer 是先进后出 这个很自然,后面的语句会依赖前面的资源,因此如果先前面的资源先 ...

  2. NFS4 挂载同主机多个目录

    写在前面的话 事情是酱婶儿的,前两天实在是帮他们查日志查的心里交瘁了,而且有些时候下班了,也就不想再接到这样的需求,于是想基于 Nginx 做一个文件下载中心,在这个文件下载中心里面存有各个服务的日志 ...

  3. HTML中&nbsp; &ensp; &emsp; &thinsp;等6种空白空格

  4. Jmeter_实现Excel文件导出到本地

    一般而言,对于页面的“导出”操作,主要经历如下两个操作:①根据数据库的内容,将文件导出到应用服务器上:②将服务器上的文件下载到本地电脑: Jmeter同LoadRunner类似,只能记录服务端与客户端 ...

  5. 【ARC077F】SS kmp+打表找规律

    Description ​ 如果某个串可以由两个一样的串前后连接得到,我们就称之为"偶串".比如说"xyzxyz"和"aaaaaa"是偶串, ...

  6. Linux常用命令--用户管理,文件权限,打包命令等

    幕布链接 Linux常用命令--用户管理,文件权限,打包命令等

  7. Selenium3+python异常后截图(screenshot)

    前言 在执行用例过程中由于是无人值守的,用例运行报错的时候,我们希望能对当前屏幕截图,留下证据. 在写用例的时候,最后一步是断言,可以把截图的动作放在断言这里,那么如何在断言失败后截图呢? 一.截图方 ...

  8. 跟我一起读postgresql源码(三)——Rewrite(查询重写模块)

    上一篇博文我们阅读了postgresql中查询分析模块的源码.查询分析模块对前台送来的命令进行词法分析.语法分析和语义分析后获得对应的查询树(Query).在获得查询树之后,程序开始对查询树进行查询重 ...

  9. bootstrap表单控件

    禁用状态: 被禁用的 fieldset 为<fieldset> 设置 disabled 属性,可以禁用 <fieldset> 中包含的所有控件. <form> &l ...

  10. mysql 面试题

    1.一张表,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把Mysql重启,再insert一条记录,这条记录的ID是18还是15 ?   2.Mysql的技术特 ...