Spring Boot 其实是一些库的集合,任意项目都可以使用它快速构建系统,更加敏捷地开发Spring应用程序,专注于应用程序的功能,不用在Spring的配置上多花功夫,甚至完全不用配置。
Idea下载 如图:
Idea 破解: 方法一 , 方法二 !
Idea Spring 项目如图:
添加相关依赖
主要涉及的文件:
pom.xml : 主要描述了项目的maven坐标,依赖关系,开发者需要遵循的规则,缺陷管理系统,组织和licenses,以及其他所有的项目相关因素,是项目级别的配置文件。
ApplicationContext.xml : spring 全局配置文件,用来控制spring 特性的。
dispatcher-servlet.xml : spring mvc里面的,控制器、拦截uri转发view。
web.xml : 站台的名称和说明、针对环境参数(Context)做初始化工作、Servlet的名称和映射、Session的设定、Tag library的对映、JSP网页设定、Mime Type处理、错误处理、利用JDNI取得站台资源。
打开IntelliJ新建工程,选择Java Enterprise -> Web Application,如图:
由于RESTful Web Service 库暂未导入,一直报错,所以使用Web Application 代替。
添加Rest环境支持:在工程根目录下创建pom.xml,然后加入代码如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.banketree</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.22.2</version>
</dependency>
</dependencies>
</project>
右键pom.xml,在菜单中选择Add as Maven Project !
项目如图:
运行配置,添加Tomcat server
运行 , 浏览器自动打开http://localhost:8080/并显示出你的jsp网页
添加Hello.java
package com.banketree;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/hello")
public class Hello {
// This method is called if TEXT_PLAIN is request
@GET
@Produces(MediaType.TEXT_PLAIN)
public String sayPlainTextHello() {
return "Hello Jersey";
}
// This method is called if XML is request
@GET
@Produces(MediaType.TEXT_XML)
public String sayXMLHello() {
return "<?xml version=/"1.0/"?>" + "<hello> Hello Jersey" + "</hello>";
}
// This method is called if HTML is request
@GET
@Produces(MediaType.TEXT_HTML)
public String sayHtmlHello() {
return "<html> " + "<title>" + "Hello Jersey" + "</title>"
+ "<body><h1>" + "Hello Jersey" + "</body></h1>" + "</html> ";
}
}
修改web/WEB-INF/web.xml内容如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>JAX-RS Servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.banketree</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Servlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
运行,访问http://localhost:8080/api/hello,看到Hello Jersey。
运行项目:
1、直接运行 main方法或者使用maven命令 “spring-boot:run”
2、java –jar roncoo-education-0.0.1-SNAPSHOT.jar
打包命令: clean package
1. @TestPropertySource 注解
2. 命令行参数
3. Java系统属性(System.getProperties())
4. 操作系统环境变量
5. 只有在random.*里包含的属性会产生一个RandomValuePropertySource
6. 在打包的jar外的应用程序配置文件(application.properties,包含YAML和profile变量)
7. 在打包的jar内的应用程序配置文件(application.properties,包含YAML和profile变量)
8. 在@Configuration类上的@PropertySource注解
9. 默认属性(使用SpringApplication.setDefaultProperties指定)
roncoo.secret=${random.value}
roncoo.number=${random.int}
roncoo.bignumber=${random.long}
roncoo.number.less.than.ten=${random.int(10)}
roncoo.number.in.range=${random.int[1024,65536]}
读取使用注解:@Value(value = "${roncoo.secret}")
注:出现黄点提示,是要提示配置元数据,可以不配置
当application.properties里的值被使用时,它们会被存在的Environment过滤,所以你能够引用先前定义的值(比如,系统属性)。
roncoo.name=www.roncoo.com
roncoo.desc=${roncoo.name} is a domain name
1. 当前目录下的一个/config子目录
2. 当前目录
3. 一个classpath下的/config包
4. classpath根路径(root)
这个列表是按优先级排序的(列表中位置高的将覆盖位置低的)
#端口配置:
server.port=8090
#时间格式化
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
#时区设置
spring.jackson.time-zone=Asia/Chongqing
注意写法:冒号后要加个空格
#自定义配置
roncoo:
secret: ${random.value}
number: ${random.int}
name: www.roncoo.com
desc: ${roncoo.name} is a domain name
#端口
server:
port: 8090
#spring jsckson
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Chongqing
Properties多环境配置
1. 配置激活选项
spring.profiles.active=dev
2.添加其他配置文件
YAML多环境配置
1.配置激活选项
spring:
profiles:
active: dev
2.在配置文件添加三个英文状态下的短横线即可区分
spring:
profiles: dev
命令运行:java -jar myapp.jar --spring.profiles.active=dev
Java Util Logging 、Log4J2 、Logback
默认是使用logback
配置方式:默认配置文件配置和引用外部配置文件配置
不建议使用:不够灵活,对log4j2等不够友好
# 日志文件名,比如:roncoo.log,或者是 /var/log/roncoo.log
logging.file=roncoo.log
# 日志级别配置,比如: logging.level.org.springframework=DEBUG
logging.level.*=info
logging.level.org.springframework=DEBUG
logback配置方式:
spring boot默认会加载classpath:logback-spring.xml或者classpath:logback-spring.groovy
使用自定义配置文件,配置方式为:
logging.config=classpath:logback-roncoo.xml
注意:不要使用logback这个来命名,否则spring boot将不能完全实例化
1.使用基于spring boot的配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/base.xml"/> <logger name="org.springframework.web" level="DEBUG"/> </configuration>
log4j配置(去除logback的依赖包,添加log4j2的依赖包)
<exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions>
<!-- 使用log4j2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
……
性能比较:Log4J2 和 Logback 都优于 log4j(不推荐使用)
配置方式:Logback最简洁,spring boot默认,推荐使用
logback:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 文件输出格式 -->
<property name="PATTERN" value="%-12(%d{yyyy-MM-dd HH:mm:ss.SSS}) |-%-5level [%thread] %c [%L] -| %msg%n" />
<!-- test文件路径 -->
<property name="TEST_FILE_PATH" value="c:/opt/roncoo/logs" />
<!-- pro文件路径 -->
<property name="PRO_FILE_PATH" value="/opt/roncoo/logs" />
<!-- 开发环境 -->
<springProfile name="dev">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<logger name="com.roncoo.education" level="debug"/>
<root level="info">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- 测试环境 -->
<springProfile name="test">
<!-- 每天产生一个文件 -->
<appender name="TEST-FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 文件路径 -->
<file>${TEST_FILE_PATH}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 文件名称 -->
<fileNamePattern>${TEST_FILE_PATH}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 文件最大保存历史数量 -->
<MaxHistory>100</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${PATTERN}</pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="TEST-FILE" />
</root>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<appender name="PROD_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PRO_FILE_PATH}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${PRO_FILE_PATH}/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
<MaxHistory>100</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${PATTERN}</pattern>
</layout>
</appender>
<root level="warn">
<appender-ref ref="PROD_FILE" />
</root>
</springProfile>
</configuration>
log4j2
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<properties>
<!-- 文件输出格式 -->
<property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%thread] %c [%L] -| %msg%n</property>
</properties>
<appenders>
<Console name="CONSOLE" target="system_out">
<PatternLayout pattern="${PATTERN}" />
</Console>
</appenders>
<loggers>
<logger name="com.roncoo.education" level="debug" />
<root level="info">
<appenderref ref="CONSOLE" />
</root>
</loggers>
</configuration>
Spring boot 在spring默认基础上,自动配置添加了以下特性:
1.包含了ContentNegotiatingViewResolver和BeanNameViewResolver beans。
2.对静态资源的支持,包括对WebJars的支持。
3.自动注册Converter,GenericConverter,Formatter beans。
4.对HttpMessageConverters的支持。
5.自动注册MessageCodeResolver。
6.对静态index.html的支持。
7.对自定义Favicon的支持。
8.主动使用ConfigurableWebBindingInitializer bean
支持的模板引擎:
FreeMarker
Thymeleaf
Velocity (1.4版本之后弃用,Spring Framework 4.3版本之后弃用)
Groovy
Mustache
注:jsp应该尽量避免使用,原因如下:
1.jsp只能打包为:war格式,不支持jar格式,只能在标准的容器里面跑(tomcat,jetty都可以)
2.内嵌的Jetty目前不支持JSPs
3.Undertow不支持jsps
4.jsp自定义错误页面不能覆盖spring boot 默认的错误页面
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
<!DOCTYPE html>
<html>
<head lang="en">
<title>Spring Boot Demo - FreeMarker</title>
<link href="/css/index.css" rel="stylesheet" />
</head>
<body>
<center>
<img src="/images/logo.png" />
<h1 id="title">${title}</h1>
</center>
<script type="text/javascript" src="/webjars/jquery/2.1.4/jquery.min.js"></script>
<script>
$(function(){
$('#title').click(function(){
alert('点击了');
});
})
</script>
</body>
</html>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
……
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head lang="en">
<title>Spring Boot Demo - FreeMarker</title>
<link href="/static/css/index.css" rel="stylesheet" />
</head>
<body>
<img src="/static/images/logo.png" alt="logo"/>
<h1 id="title">${title}</h1>
<c:url value="http://www.roncoo.com" var="url"/>
<spring:url value="http://www.roncoo.com" htmlEscape="true" var="springUrl" />
Spring URL: ${springUrl}
<br>
JSTL URL: ${url}
<!-- <script type="text/javascript" src="/static/webjars/jquery/2.1.4/jquery.min.js"></script>
<script>
$(function(){
$('#title').click(function(){
alert('点击了');
});
})
</script> -->
</body>
</html>
……
@Controller
@RequestMapping(value = "error")
public class BaseErrorController implements ErrorController {
private static final Logger logger = LoggerFactory.getLogger(BaseErrorController.class);
@Override
public String getErrorPath() {
logger.info("出错啦!进入自定义错误控制器");
return "error/error";
}
@RequestMapping
public String error() {
return getErrorPath();
}
}
2.1 html静态页面:在resources/public/error/ 下定义 如添加404页面: resources/public/error/404.html页面,中文注意页面编码 2.2 模板引擎页面:在templates/error/下定义 如添加5xx页面: templates/error/5xx.ftl 注:templates/error/ 这个的优先级比较 resources/public/error/高
/**
* 统一异常处理
*
* @param exception
* exception
* @return
*/
@ExceptionHandler({ RuntimeException.class })
@ResponseStatus(HttpStatus.OK)
public ModelAndView processException(RuntimeException exception) {
logger.info("自定义异常处理-RuntimeException");
ModelAndView m = new ModelAndView();
m.addObject("roncooException", exception.getMessage());
m.setViewName("error/500");
return m;
}
/**
* 统一异常处理
*
* @param exception
* exception
* @return
*/
@ExceptionHandler({ Exception.class })
@ResponseStatus(HttpStatus.OK)
public ModelAndView processException(Exception exception) {
logger.info("自定义异常处理-Exception");
ModelAndView m = new ModelAndView();
m.addObject("roncooException", exception.getMessage());
m.setViewName("error/500");
return m;
}
Servlet是用来处理客户端请求的动态资源,也就是当我们在浏览器中键入一个地址回车跳转后,请求就会被发送到对应的Servlet上进行处理。
Servlet的任务有:
接收请求数据:我们都知道客户端请求会被封装成HttpServletRequest对象,里面包含了请求头、参数等各种信息。
处理请求:通常我们会在service、doPost或者doGet方法进行接收参数,并且调用业务层(service)的方法来处理请求。
完成响应:处理完请求后,我们一般会转发(forward)或者重定向(redirect)到某个页面,转发是HttpServletRequest中的方法,
重定向是HttpServletResponse中的方法,两者是有很大区别的。
Servlet的创建:Servlet可以在第一次接收请求时被创建,也可以在在服务器启动时就被创建,这需要在web.xml的< servlet>中添加一条配置信息
< load-on-startup>5< /load-on-startup>,当值为0或者大于0时,表示容器在应用启动时就加载这个servlet,当是一个负数时或者没有指定时,
则指示容器在该servlet被请求时才加载。
Servlet的生命周期方法:
> void init(ServletConfig)
servlet的初始化方法,只在创建servlet实例时候调用一次,Servlet是单例的,整个服务器就只创建一个同类型Servlet
> void service(ServletRequest,ServletResponse)
servlet的处理请求方法,在servle被请求时,会被马上调用,每处理一次请求,就会被调用一次。ServletRequest类为请求类,ServletResponse类为响应类
> void destory()
servlet销毁之前执行的方法,只执行一次,用于释放servlet占有的资源,通常Servlet是没什么可要释放的,所以该方法一般都是空的
Servlet的其他重要方法:
> ServletConfig getServletConfig()
获取servlet的配置信息的方法,所谓的配置信息就是WEB-INF目录下的web.xml中的servlet标签里面的信息
> String getServletInfo()
获取servlet的信息方法
Servlet的配置:
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.briup.estore.web.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
……
filter与servlet在很多的方面极其相似,但是也有不同,例如filter和servlet一样都又三个生命周期方法,同时他们在web.xml中的配置文件也是差不多的、 但是servlet主要负责处理请求,而filter主要负责拦截请求,和放行。 filter四种拦截方式 REQUEST:直接访问目标资源时执行过滤器。包括:在地址栏中直接访问、表单提交、超链接、重定向,只要在地址栏中可以看到目标资源的路径,就是REQUEST; FORWARD:转发访问执行过滤器。包括RequestDispatcher#forward()方法、< jsp:forward>标签都是转发访问; INCLUDE:包含访问执行过滤器。包括RequestDispatcher#include()方法、< jsp:include>标签都是包含访问; ERROR:当目标资源在web.xml中配置为< error-page>中时,并且真的出现了异常,转发到目标资源时,会执行过滤器。 url-mapping的写法 匹配规则有三种: 精确匹配 —— 如/foo.htm,只会匹配foo.htm这个URL 路径匹配 —— 如/foo/*,会匹配以foo为前缀的URL 后缀匹配 —— 如*.htm,会匹配所有以.htm为后缀的URL < url-pattern>的其他写法,如/foo/ ,/.htm ,/foo 都是不对的。 执行filter的顺序 如果有多个过滤器都匹配该请求,顺序决定于web.xml filter-mapping的顺序,在前面的先执行,后面的后执行
……
Listener就是监听器,我们在JavaSE开发或者Android开发时,经常会给按钮加监听器,当点击这个按钮就会触发监听事件,调用onClick方法, 本质是方法回调。在JavaWeb的Listener也是这么个原理,但是它监听的内容不同,它可以监听Application、Session、Request对象, 当这些对象发生变化就会调用对应的监听方法。 应用域监听: Ø ServletContext(监听Application) ¨ 生命周期监听:ServletContextListener,它有两个方法,一个在出生时调用,一个在死亡时调用; void contextInitialized(ServletContextEvent sce):创建Servletcontext时 void contextDestroyed(ServletContextEvent sce):销毁Servletcontext时 ¨ 属性监听:ServletContextAttributeListener,它有三个方法,一个在添加属性时调用,一个在替换属性时调用,最后一个是在移除属性时调用。 void attributeAdded(ServletContextAttributeEvent event):添加属性时; void attributeReplaced(ServletContextAttributeEvent event):替换属性时; void attributeRemoved(ServletContextAttributeEvent event):移除属性时; Ø HttpSession(监听Session) ¨ 生命周期监听:HttpSessionListener,它有两个方法,一个在出生时调用,一个在死亡时调用; voidsessionCreated(HttpSessionEvent se):创建session时 void sessionDestroyed(HttpSessionEvent se):销毁session时 ¨ 属性监听:HttpSessioniAttributeListener,它有三个方法,一个在添加属性时调用,一个在替换属性时调用,最后一个是在移除属性时调用。 void attributeAdded(HttpSessionBindingEvent event):添加属性时; void attributeReplaced(HttpSessionBindingEvent event):替换属性时 void attributeRemoved(HttpSessionBindingEvent event):移除属性时 Ø ServletRequest(监听Request) ¨ 生命周期监听:ServletRequestListener,它有两个方法,一个在出生时调用,一个在死亡时调用; voidrequestInitialized(ServletRequestEvent sre):创建request时 void requestDestroyed(ServletRequestEvent sre):销毁request时 ¨ 属性监听:ServletRequestAttributeListener,它有三个方法,一个在添加属性时调用,一个在替换属性时调用,最后一个是在移除属性时调用。 voidattributeAdded(ServletRequestAttributeEvent srae):添加属性时 void attributeReplaced(ServletRequestAttributeEvent srae):替换属性时 void attributeRemoved(ServletRequestAttributeEvent srae):移除属性时 感知Session监听: 1:HttpSessionBindingListener监听 ⑴在需要监听的实体类实现HttpSessionBindingListener接口 ⑵重写valueBound()方法,这方法是在当该实体类被放到Session中时,触发该方法 ⑶重写valueUnbound()方法,这方法是在当该实体类从Session中被移除时,触发该方法 2:HttpSessionActivationListener监听 ⑴在需要监听的实体类实现HttpSessionActivationListener接口 ⑵重写sessionWillPassivate()方法,这方法是在当该实体类被序列化时,触发该方法 ⑶重写sessionDidActivate()方法,这方法是在当该实体类被反序列化时,触发该方法
方法一: 通过注册 ServletRegistrationBean、 FilterRegistrationBean 和 ServletListenerRegistrationBean 获得控制
public class CustomServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("servlet get method");
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("servlet post method");
response.getWriter().write("hello world");
}
}
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init filter");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("do filter");
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("destroy filter");
}
}
public class CustomListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed");
}
}
注册 bean
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new CustomServlet(), "/roncoo");
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
return new FilterRegistrationBean(new CustomFilter(), servletRegistrationBean());
}
@Bean
public ServletListenerRegistrationBean<CustomListener> servletListenerRegistrationBean() {
return new ServletListenerRegistrationBean<CustomListener>(new CustomListener());
}
方法二: 通过实现 ServletContextInitializer 接口直接注册
implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws
ServletException {
servletContext.addServlet("customServlet", new
CustomServlet()).addMapping("/roncoo");
servletContext.addFilter("customFilter", new
CustomFilter())
.addMappingForServletNames(EnumSet.of(DispatcherType.REQUES
T), true, "customServlet");
servletContext.addListener(new CustomListener());
}
方法三: 在 SpringBootApplication 上使用@ServletComponentScan 注解后,直接通过@WebServlet、
@WebFilter、 @WebListener 注解自动注册
Cross-Origin Resource Sharing(CORS)跨来源资源共享是一份浏览器技术的规范,提供了 Web 服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略,是 JSONP 模式的现代版。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以让网页设计师用一般的 XMLHttpRequest,这种方式的错误处理比 JSONP 要来的好。另一方面,JSONP 可以在不支持 CORS 的老旧浏览器上运作。现代的浏览器都支持 CORS。
一、 Web 开发经常会遇到跨域问题解决方案有: jsonp, iframe,CORS 等等
CORS 与 JSONP 相比
1、 JSONP 只能实现 GET 请求,而 CORS 支持所有类型的 HTTP 请求。
2、 使用 CORS,开发者可以使用普通的 XMLHttpRequest 发起请求和获得数据,比起 JSONP 有更好的
错误处理。
3、 JSONP 主要被老的浏览器支持,它们往往不支持 CORS,而绝大多数现代浏览器都已经支持了 CORS
浏览器支持情况
Chrome 3+
Firefox 3.5+
Opera 12+
Safari 4+
Internet Explorer 8+
二、 在 spring MVC 中可以配置全局的规则,也可以使用@CrossOrigin 注解进行细粒度的配置。
全局配置:
@Configuration
public class CustomCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**").allowedOrigins("http://localhost:8080");
}
};
}
}
或者是
/**
* 全局设置
*/
@Configuration
public class CustomCorsConfiguration2 extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**").allowedOrigins("http://localhost:8080");
}
}
定义方法:
@RestController
@RequestMapping("/api")
public class ApiController {
@RequestMapping(value = "/get")
public HashMap<String, Object> get(@RequestParam String name) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title", "hello world");
map.put("name", name);
return map;
}
}
测试 js:
$.ajax({
url: "http://localhost:8081/api/get",
type: "POST",
data: {
name: "测试"
},
success: function(data, status, xhr) {
console.log(data);
alert(data.name);
}
});
细粒度配置
@RestController
@RequestMapping(value = "/api", method = RequestMethod.POST)
public class ApiController {
@CrossOrigin(origins = "http://localhost:8080")
@RequestMapping(value = "/get")
public HashMap<String, Object> get(@RequestParam String name) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title", "hello world");
map.put("name", name);
return map;
}
}
Spring Boot 默认使用 springMVC 包装好的解析器进行上传
<form method="POST" enctype="multipart/form-data" action="/file/upload">
文件: <input type="file" name="roncooFile" />
<input type="submit" value="上传" />
</form>
@Controller
@RequestMapping(value = "/file")
public class FileController {
private static final Logger logger = LoggerFactory.getLogger(FileController.class);
@RequestMapping(value = "upload")
@ResponseBody
public String upload(@RequestParam("roncooFile") MultipartFile file) {
if (file.isEmpty()) {
return "文件为空";
}
// 获取文件名
String fileName = file.getOriginalFilename();
logger.info("上传的文件名为: " + fileName);
// 获取文件的后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));
logger.info("上传的后缀名为: " + suffixName);
// 文件上传路径
String filePath = "d:/roncoo/ttt/";
// 解决中文问题, liunx 下中文路径,图片显示问题
// fileName = UUID.randomUUID() + suffixName;
File dest = new File(filePath + fileName);
// 检测是否存在目录
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
file.transferTo(dest);
return "上传成功";
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "上传失败";
}
}
配置
spring.http.multipart.enabled=true #默认支持文件上传.
spring.http.multipart.file-size-threshold=0 #支持文件写入磁盘.
spring.http.multipart.location= # 上传文件的临时目录
spring.http.multipart.max-file-size=1Mb # 最大支持文件大小
spring.http.multipart.max-request-size=10Mb # 最大支持请求大小
配置数据源: 嵌入式数据库的支持: Spring Boot 可以自动配置 H2, HSQL and Derby 数据库, 不需要提供任何的
链接 URLs, 只需要加入相应的 jar 包, Spring boot 可以自动发现装配 。
<!-- 数据库 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> mysql spring.datasource.url=jdbc:mysql://localhost/spring_boot_demo?useUnicode=true&character Encoding=utf-8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver 注: 1.可以不指定 driver-class-name, spring boot 会自动识别 url。 2.数据连接池默认使用 tomcat-jdbc 连接池的配置: spring.datasource.tomcat.*
JdbcTemplate 模板:
// 自动注册 @Autowired private JdbcTemplate jdbcTemplate;
脚本:
CREATE TABLE `roncoo_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';
实体类:
public class RoncooUser {
private int id;
private String name;
private Date createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "RoncooUser [id=" + id + ", name=" + name + ", createTime=" + createTime
+ "]";
}
}
接口 :
int insert(RoncooUser roncooUser); int deleteById(int id); int updateById(RoncooUser roncooUser); RoncooUser selectById(int id);
实现类代码:
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int insert(RoncooUser roncooUser) {
String sql = "insert into roncoo_user (name, create_time) values (?, ?)";
return jdbcTemplate.update(sql, roncooUser.getName(),
roncooUser.getCreateTime());
}
@Override
public int deleteById(int id) {
String sql = "delete from roncoo_user where id=?";
return jdbcTemplate.update(sql, id);
}
@Override
public int updateById(RoncooUser roncooUser) {
String sql = "update roncoo_user set name=?, create_time=? where id=?";
return jdbcTemplate.update(sql, roncooUser.getName(),
roncooUser.getCreateTime(), roncooUser.getId());
}
@Override
public RoncooUser selectById(int id) {
String sql = "select * from roncoo_user where id=?";
return jdbcTemplate.queryForObject(sql, new RowMapper<RoncooUser>() {
@Override
public RoncooUser mapRow(ResultSet rs, int rowNum) throws SQLException {
RoncooUser roncooUser = new RoncooUser();
roncooUser.setId(rs.getInt("id"));
roncooUser.setName(rs.getString("name"));
roncooUser.setCreateTime(rs.getDate("create_time"));
return roncooUser;
}
}, id);
}
封装 spring jdbc, 带分页:
/**
* 获取当前事务最后一次更新的主键值
*/
public Long getLastId() {
return jdbcTemplate.queryForObject("select last_insert_id() as id", Long.class);
}
/**
* 获取对象信息
*/
public <T> T queryForObject(String sql, Class<T> clazz, Object... args) {
Assert.hasText(sql, "sql 语句不能为空");
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<T>(clazz), args);
}
/**
* 获取对象集合信息
*/
public <T> List<T> queryForObjectList(String sql, Class<T> clazz, Object... args) {
Assert.hasText(sql, "sql 语句不能为空");
return jdbcTemplate.query(sql, args, new BeanPropertyRowMapper<T>(clazz));
}
/**
* 分页, jdbcTemplate 不支持 like 自定义,只能拼装
*/
public Page<Map<String, Object>> queryForPage(String sql, int pageCurrent, int
pageSize, Object... args) {
Assert.hasText(sql, "sql 语句不能为空");
Assert.isTrue(pageCurrent >= 1, "pageNo 必须大于等于 1");
String sqlCount = Sql.countSql(sql);
int count = jdbcTemplate.queryForObject(sqlCount, Integer.class, args);
pageCurrent = Sql.checkPageCurrent(count, pageSize, pageCurrent);
pageSize = Sql.checkPageSize(pageSize);
int totalPage = Sql.countTotalPage(count, pageSize);
String sqlList = sql + Sql.limitSql(count, pageCurrent, pageSize);
List<Map<String, Object>> list = jdbcTemplate.queryForList(sqlList, args);
return new Page<Map<String, Object>>(count, totalPage, pageCurrent, pageSize,
list);
}
/**
* 分页, jdbcTemplate 不支持 like 是定义,只能拼装
*/
public <T> Page<T> queryForPage(String sql, int pageCurrent, int pageSize, Class<T>
clazz, Object... args) {
Assert.hasText(sql, "sql 语句不能为空");
Assert.isTrue(pageCurrent >= 1, "pageNo 必须大于等于 1");
Assert.isTrue(clazz != null, "clazz 不能为空");
String sqlCount = Sql.countSql(sql);
int count = jdbcTemplate.queryForObject(sqlCount, Integer.class, args);
pageCurrent = Sql.checkPageCurrent(count, pageSize, pageCurrent);
pageSize = Sql.checkPageSize(pageSize);
int totalPage = Sql.countTotalPage(count, pageSize);
String sqlList = sql + Sql.limitSql(count, pageCurrent, pageSize);
List<T> list = jdbcTemplate.query(sqlList, new BeanPropertyRowMapper<T>(clazz),
args);
return new Page<T>(count, totalPage, pageCurrent, pageSize, list);
}
……
依赖
<!-- 数据库 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
配置
# JPA spring.jpa.hibernate.ddl-auto= update #显示 sql 语句 spring.jpa.show-sql=true
实体类
@Entity
public class RoncooUserLog {
@Id
@GeneratedValue
private Integer id;
@Column
private Date createTime;
@Column
private String userName;
@Column
private String userIp;
……
定义接口(继承 JpaRepository)
public interface RoncooUserLogDao extends JpaRepository<RoncooUserLog, Integer>{
}
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,
要么完全不起作用。
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状
态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,
防止数据损坏。
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从
任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运
行,也可能开启一个新事务,并在自己的事务中运行。
Spring 定义了七种传播行为:
PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运
行。否则,会启动一个新的事务, Spring 默认使用
PROPAGATION_SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会
在这个事务中运行
PROPAGATION_MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果
存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用 JTATransactionManager 的话,则需要
访问 TransactionManager
PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期
间,当前事务将被挂起。如果使用 JTATransactionManager 的话,则需要访问 TransactionManager
PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛
出异常
PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务
可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与
PROPAGATION_REQUIRED 一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的
文档来确认它们是否支持嵌套事务。
隔离级别
隔离级别定义了一个事务可能受其他并发事务影响的程度。
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别, Spring 默认使用, mysql 默认的隔离级别为:
Repeatable Read(可重复读)
ISOLATION_READ_UNCOMMITTED 读未提交, 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致
脏读、幻读或不可重复读ISOLATION_READ_COMMITTED 读已提交, 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读 或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ 可重复读, 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生ISOLATION_SERIALIZABLE 可串行化, 最高的隔离级别,完全服从 ACID 的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的脏读(Dirty reads) ——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写再稍后被回滚了,那么第一个事务获取的数据就是无效的。
不可重复读(Nonrepeatable read) ——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
幻读(Phantom read) ——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
属性说明 @Transactional
a、 isolation:用于指定事务的隔离级别。默认为底层事务的隔离级别。
b、 noRollbackFor:指定遇到指定异常时强制不回滚事务。
c、 noRollbackForClassName:指定遇到指定多个异常时强制不回滚事务。该属性可以指定多个异常类
名。
d、 propagation:指定事务的传播属性。
e、 readOnly:指定事务是否只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优
化事务。若真的是一个只读取的数据库应设置 readOnly=true
f、 rollbackFor:指定遇到指定异常时强制回滚事务。
g、 rollbackForClassName:指定遇到指定多个异常时强制回滚事务。该属性可以指定多个异常类名。
h、 timeout:指定事务的超时时长。
注意:
1.mysql 为例, 存储引擎不能使用 MyISAM,应该使用 InnoDB
实现代码
@Service
public class UserService {
@Autowired
private RoncooUserDao roncooUserDao;
@Autowired
private RoncooUserLogDao roncooUserLogDao;
/**
* 用户注册
*
*/
@Transactional
public String register(String name, String ip) {
// 1.添加用户
RoncooUser roncooUser = new RoncooUser();
roncooUser.setName(name);
roncooUser.setCreateTime(new Date());
roncooUserDao.insert(roncooUser);
// 测试使用
boolean flag = true;
if (flag) {
throw new RuntimeException();
}
// 2.添加注册日志
RoncooUserLog roncooUserLog = new RoncooUserLog();
roncooUserLog.setUserName(name);
roncooUserLog.setUserIp(ip);
roncooUserLog.setCreateTime(new Date());
roncooUserLogDao.save(roncooUserLog);
return "success";
}
}
添加依赖
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
配置
spring.datasource.url=jdbc:h2:~/test;AUTO_SERVER=TRUE;DB_CLOSE _ON_EXIT=FALSE spring.datasource.username=sa spring.datasource.password= 注: 1."~"这个符号代表的就是当前登录到操作系统的用户对应的用户目录 2.账号密码我们指定之后, 就会自动创建 指定路径: spring.datasource.url=jdbc:h2:file:D:/roncoo_h2/roncoo_spring_ boot;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE 内存模式: spring.datasource.url=jdbc:h2:mem:test
控制台
路径: http://localhost:8080/h2-console
redis windows 版本下载
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置文件
#redis spring.redis.host=localhost spring.redis.port=6379 #spring.redis.password=123456 #spring.redis.database=0 #spring.redis.pool.max-active=8 #spring.redis.pool.max-idle=8 #spring.redis.pool.max-wait=-1 #spring.redis.pool.min-idle=0 #spring.redis.timeout=0测试
@Component
public class RoncooRedisComponent {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void set(String key, String value) {
ValueOperations<String, String> ops = this.stringRedisTemplate.opsForValue();
if (!this.stringRedisTemplate.hasKey(key)) {
ops.set(key, value);
System.out.println("set key success");
} else {
// 存在则打印之前的 value 值
System.out.println("this key = " + ops.get(key));
}
}
public String get(String key) {
return this.stringRedisTemplate.opsForValue().get(key);
}
public void del(String key) {
this.stringRedisTemplate.delete(key);
}
}
@Autowired
private RoncooRedisComponent roncooRedisComponent;
@Test
public void set() {
roncooRedisComponent.set("roncoo", "hello world");
}
@Test
public void get() {
System.out.println(roncooRedisComponent.get("roncoo"));
}
@Test
public void del() {
roncooRedisComponent.del("roncoo");
}
安装: mongodb 下载链接
下载版本: mongodb-win32-x86_64-2008plus-ssl-3.2.9-signed.msi
安装出现 2502、 2503 错误 解决办法
启动命令: mongod.exe --dbpath d:/roncoo_mongodb/
指定路径: --dbpath
注: 要先创建文件夹
添加依赖
<!-- mongodb --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>配置文件
# MONGODB (MongoProperties) spring.data.mongodb.uri=mongodb://localhost/test spring.data.mongodb.port=27017 #spring.data.mongodb.authentication-database= #spring.data.mongodb.database=test #spring.data.mongodb.field-naming-strategy= #spring.data.mongodb.grid-fs-database= #spring.data.mongodb.host=localhost #spring.data.mongodb.password= #spring.data.mongodb.repositories.enabled=true #spring.data.mongodb.username=
代码
@Component
public class RoncooMongodbComponent {
@Autowired
private MongoTemplate mongoTemplate;
public void insert(RoncooUser roncooUser) {
mongoTemplate.insert(roncooUser);
}
public void deleteById(int id) {
Criteria criteria = Criteria.where("id").in(id);
Query query = new Query(criteria);
mongoTemplate.remove(query, RoncooUser.class);
}
public void updateById(RoncooUser roncooUser) {
Criteria criteria = Criteria.where("id").in(roncooUser.getId());
Query query = new Query(criteria);
Update update = new Update();
update.set("name", roncooUser.getName());
update.set("createTime", roncooUser.getCreateTime());
mongoTemplate.updateMulti(query, update, RoncooUser.class);
}
public RoncooUser selectById(int id) {
Criteria criteria = Criteria.where("id").in(id);
Query query = new Query(criteria);
return mongoTemplate.findOne(query, RoncooUser.class);
}
}
设置日志打印: <logger name="org.springframework.data.mongodb.core.MongoTemplate" level="debug"/>
@Autowired
private RoncooMongodbComponent roncooMongodbComponent;
@Test
public void set() {
RoncooUser roncooUser = new RoncooUser();
roncooUser.setId(1);
roncooUser.setName("无境1");
roncooUser.setCreateTime(new Date());
roncooMongodbComponent.insert(roncooUser);
}
@Test
public void select() {
System.out.println(roncooMongodbComponent.selectById(1));
}
@Test
public void update() {
RoncooUser roncooUser = new RoncooUser();
roncooUser.setId(1);
roncooUser.setName("测试修改");
roncooUser.setCreateTime(new Date());
roncooMongodbComponent.updateById(roncooUser);
System.out.println(roncooMongodbComponent.selectById(1));
}
@Test
public void delete() {
roncooMongodbComponent.deleteById(1);
}
四、 使用: MongoRepository
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.roncoo.example.bean.RoncooUserLog;
public interface RoncooUserLogMongoDao extends MongoRepository<RoncooUserLog, Integer>{
RoncooUserLog findByUserName(String string);
RoncooUserLog findByUserNameAndUserIp(String string, String ip);
Page<RoncooUserLog> findByUserName(String string, Pageable pageable);
}
测试
@Autowired
private RoncooUserLogMongoDao roncooUserLogMongoDao;
@Test
public void insert() {
RoncooUserLog entity = new RoncooUserLog();
entity.setId(1);
entity.setUserName("无境");
entity.setUserIp("192.168.0.1");
entity.setCreateTime(new Date());
roncooUserLogMongoDao.save(entity);
}
@Test
public void delete() {
roncooUserLogMongoDao.delete(1);
}
@Test
public void update() {
RoncooUserLog entity = new RoncooUserLog();
entity.setId(1);
entity.setUserName("无境2");
entity.setUserIp("192.168.0.1");
entity.setCreateTime(new Date());
roncooUserLogMongoDao.save(entity);
}
@Test
public void select() {
RoncooUserLog result = roncooUserLogMongoDao.findOne(1);
System.out.println(result);
}
@Test
public void select2() {
RoncooUserLog result = roncooUserLogMongoDao.findByUserName("
无境2");
System.out.println(result);
}
// 分页
@Test
public void queryForPage() {
Pageable pageable = new PageRequest(0, 20, new Sort(new
Order(Direction.DESC, "id")));
// Page<RoncooUserLog> result =
roncooUserLogDao.findByUserName("无境2", pageable);
Page<RoncooUserLog> result =
roncooUserLogMongoDao.findAll(pageable);
System.out.println(result.getContent());
}
Spring boot 支持的缓存:
Generic、JCache (JSR-107)、EhCache 2.x、Hazelcast、Infinispan、Couchbase、Redis、Caffeine、Guava、Simple
添加依赖
<!-- caching --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
配置信息
spring.cache.type=ehcache spring.cache.ehcache.config=classpath:config/ehcache.xml
ehcache.xml <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <cache name="roncooCache" eternal="false" maxEntriesLocalHeap="0" timeToIdleSeconds="50"></cache> <!-- eternal: true表示对象永不过期,此时会忽略timeToIdleSeconds和 timeToLiveSeconds属性,默认为false --> <!-- maxEntriesLocalHeap:堆内存中最大缓存对象数, 0没有限制 --> <!-- timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为 单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了 timeToIdleSeconds属性值,这个对象就会过期, EHCache将把它从缓存中清空。 只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以 无限期地处于空闲状态 --> </ehcache>
注解
@EnableCaching: 启用缓存注解
接口
public interface RoncooUserLogCache {
/**
* 查询
*
* @param id
* @return
*/
RoncooUserLog selectById(Integer id);
/**
* 更新
*
* @param roncooUserLog
* @return
*/
RoncooUserLog updateById(RoncooUserLog roncooUserLog);
/**
* 删除
*
* @param id
* @return
*/
String deleteById(Integer id);
}
实现
@CacheConfig(cacheNames = "roncooCache")
@Repository
public class RoncooUserLogCacheImpl implements
RoncooUserLogCache {
@Autowired
private RoncooUserLogDao roncooUserLogDao;
@Cacheable(key = "#p0")
@Override
public RoncooUserLog selectById(Integer id) {
System.out.println("查询功能,缓存找不到,直接读库, id=" +
id);
return roncooUserLogDao.findOne(id);
}
@CachePut(key = "#p0")
@Override
public RoncooUserLog updateById(RoncooUserLog
roncooUserLog) {
System.out.println("更新功能,更新缓存,直接写库, id=" +
roncooUserLog);
return roncooUserLogDao.save(roncooUserLog);
}
@CacheEvict(key = "#p0")
@Override
public String deleteById(Integer id) {
System.out.println("删除功能,删除缓存,直接写库, id=" + id);
return "清空缓存成功";
}
}
注解说明: @CacheConfig: 缓存配置 @Cacheable: 应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调 用方法获取数据,然后把数据添加到缓存中。 适用于查找 @CachePut: 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的 是,它每次都会触发真实方法的调用。 适用于更新和插入 @CacheEvict: 主要针对方法配置,能够根据一定的条件对缓存进行清空。 适用于删除
测试
@RequestMapping(value = "/select", method = RequestMethod.GET)
public RoncooUserLog get(@RequestParam(defaultValue = "1") Integer id) {
return RoncooUserLogCache.selectById(id);
}
@RequestMapping(value = "/update", method = RequestMethod.GET)
public RoncooUserLog update(@RequestParam(defaultValue = "1") Integer id) {
RoncooUserLog bean = RoncooUserLogCache.selectById(id);
bean.setUserName("测试");
bean.setCreateTime(new Date());
RoncooUserLogCache.updateById(bean);
return bean;
}
@RequestMapping(value = "/del", method = RequestMethod.GET)
public String del(@RequestParam(defaultValue = "1") Integer id) {
return RoncooUserLogCache.deleteById(id);
}
添加依赖
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency>
配置文件
spring.cache.type=redis
缓存使用优先级问题
1.默认按照 spring boot 的加载顺序来实现
2.配置文件优先于默认
自定义缓存管理器
/**
* redis 自定义缓存管理器
*/
@Configuration
public class RedisCacheConfiguration extends CachingConfigurerSupport {
/**
* 自定义缓存管理器.
*
* @param redisTemplate
* @return
*/
@Bean
public CacheManager cacheManager(RedisTemplate<?, ?> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// 设置默认的过期时间
cacheManager.setDefaultExpiration(20);
Map<String, Long> expires = new HashMap<String, Long>();
// 单独设置
expires.put("roncooCache", 200L);
cacheManager.setExpires(expires);
return cacheManager;
}
自定义 key 的生成策略
/**
* 自定义 key. 此方法将会根据类名+方法名+所有参数的值生成唯一的一个 key,即使@Cacheable 中
的 value 属性一样, key 也会不一样。
*/
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(method.getName());
for (Object obj : objects) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
Spring Boot支持的jms有:ActiveMQ、Artemis、HornetQ
1、异步消息服务 JMS(ActiveMQ)
添加依赖
<!-- jms --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency>
配置信息
# ACTIVEMQ (ActiveMQProperties) spring.activemq.in-memory=true #spring.activemq.broker-url= #spring.activemq.password= #spring.activemq.user= #spring.activemq.packages.trust-all=false #spring.activemq.packages.trusted= #spring.activemq.pool.configuration.*= #spring.activemq.pool.enabled=false #spring.activemq.pool.expiry-timeout=0 #spring.activemq.pool.idle-timeout=30000 #spring.activemq.pool.max-connections=1
启动注解
@EnableJms 添加在main方法里面
/**
* jms 队列配置
*/
@Configuration
public class JmsConfiguration {
@Bean
public Queue queue() {
return new ActiveMQQueue("roncoo.queue");
}
}
代码
/**
*/
@Component
public class RoncooJmsComponent {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
public void send(String msg) {
this.jmsMessagingTemplate.convertAndSend(this.queue, msg);
}
@JmsListener(destination = "roncoo.queue")
public void receiveQueue(String text) {
System.out.println("接受到:" + text);
}
}
测试
@Autowired
private RoncooJmsComponent roncooJmsComponent;
@Test
public void send() {
roncooJmsComponent.send("hello world");
}
RabbitMQ下载地址
erlang 下载地址
添加依赖
<!-- amqp --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
配置信息
# RABBIT (RabbitProperties) #spring.rabbitmq.host=localhost #spring.rabbitmq.port=5672 #spring.rabbitmq.password= #spring.rabbitmq.username=
1.启用注解: @EnableRabbit
2.配置
/**
* amqp 队列配置
*/
@Configuration
public class AmqpConfiguration {
@Bean
public Queue queue() {
return new Queue("roncoo.queue");
}
}
3.
/**
*/
@Component
public class RoncooAmqpComponent {
@Autowired
private AmqpTemplate amqpTemplate;
public void send(String msg) {
this.amqpTemplate.convertAndSend("roncoo.queue", msg);
}
@RabbitListener(queues = "roncoo.queue")
public void receiveQueue(String text) {
System.out.println("接受到:" + text);
}
}
测试
@Autowired
private RoncooAmqpComponent roncooAmqpComponent;
@Test
public void send() {
roncooAmqpComponent.send("hello world2");
}
添加依赖
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
代码
/**
*/
@RestController
@RequestMapping(value = "/rest", method = RequestMethod.POST)
public class RestRoncooController {
@Autowired
private RoncooUserLogCache RoncooUserLogCache;
@RequestMapping(value = "/update")
public RoncooUserLog update(@RequestBody JsonNode jsonNode) {
System.out.println("jsonNode=" + jsonNode);
RoncooUserLog bean = RoncooUserLogCache.selectById(jsonNode.get("id").asInt(1));
if(bean == null){
bean = new RoncooUserLog();
}
bean.setUserName("测试");
bean.setCreateTime(new Date());
bean.setUserIp("192.168.1.1");
RoncooUserLogCache.updateById(bean);
return bean;
}
@RequestMapping(value = "/update/{id}", method = RequestMethod.GET)
public RoncooUserLog update2(@PathVariable(value = "id") Integer id) {
RoncooUserLog bean = RoncooUserLogCache.selectById(id);
if(bean == null){
bean = new RoncooUserLog();
}
bean.setUserName("测试");
bean.setCreateTime(new Date());
bean.setUserIp("192.168.1.1");
RoncooUserLogCache.updateById(bean);
return bean;
}
}
测试
@Autowired
private RestTemplateBuilder restTemplateBuilder;
/**
* get请求
*/
@Test
public void getForObject() {
RoncooUserLog bean = restTemplateBuilder.build().getForObject("http://localhost:8080/rest/update/{id}", RoncooUserLog.class, 6);
System.out.println(bean);
Map<String,Object> map = new HashMap<String,Object>();
map.put("id", 7);
bean = restTemplateBuilder.build().postForObject("http://localhost:8080/rest/update", map, RoncooUserLog.class);
System.out.println(bean);
}
代理实现:
static class ProxyCustomizer implements RestTemplateCustomizer {
@Override
public void customize(RestTemplate restTemplate) {
String proxyHost = "59.33.46.187";
int proxyPort = 6969;
HttpHost proxy = new HttpHost(proxyHost, proxyPort);
HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
@Override
public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
return super.determineProxy(target, request, context);
}
}).build();
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
httpComponentsClientHttpRequestFactory.setConnectTimeout(10000);
httpComponentsClientHttpRequestFactory.setReadTimeout(60000);
restTemplate.setRequestFactory(httpComponentsClientHttpRequestFactory);
}
}
代理测试:
String result = restTemplateBuilder.additionalCustomizers(new ProxyCustomizer()).build().getForObject("http://www.roncoo.com", String.class);
System.out.println(result);
在线代理:
http://ip.zdaye.com/
添加依赖
<!-- mail --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
配置
# mail spring.mail.host: smtp.exmail.qq.com spring.mail.username:fengyw@roncoo.com,service@roncoo.com,education@roncoo.com spring.mail.password: spring.mail.properties.mail.smtp.auth: true # 企业qq的邮箱或者是163这类,不建议使用私人qq
代码
实现多账号
/**
* 实现多账号,轮询发送
*/
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class RoncooJavaMailSenderImpl extends JavaMailSenderImpl implements JavaMailSender {
private ArrayList<String> usernameList;
private ArrayList<String> passwordList;
private int currentMailId = 0;
private final MailProperties properties;
public RoncooJavaMailSenderImpl(MailProperties properties) {
this.properties = properties;
// 初始化账号
if (usernameList == null)
usernameList = new ArrayList<String>();
String[] userNames = this.properties.getUsername().split(",");
if (userNames != null) {
for (String user : userNames) {
usernameList.add(user);
}
}
// 初始化密码
if (passwordList == null)
passwordList = new ArrayList<String>();
String[] passwords = this.properties.getPassword().split(",");
if (passwords != null) {
for (String pw : passwords) {
passwordList.add(pw);
}
}
}
@Override
protected void doSend(MimeMessage[] mimeMessage, Object[] object) throws MailException {
super.setUsername(usernameList.get(currentMailId));
super.setPassword(passwordList.get(currentMailId));
// 设置编码和各种参数
super.setHost(this.properties.getHost());
super.setDefaultEncoding(this.properties.getDefaultEncoding().name());
super.setJavaMailProperties(asProperties(this.properties.getProperties()));
super.doSend(mimeMessage, object);
// 轮询
currentMailId = (currentMailId + 1) % usernameList.size();
}
private Properties asProperties(Map<String, String> source) {
Properties properties = new Properties();
properties.putAll(source);
return properties;
}
@Override
public String getUsername() {
return usernameList.get(currentMailId);
}
}
实现发送功能
/**
*/
@Component
public class RoncooJavaMailComponent {
private static final String template = "mail/roncoo.ftl";
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
@Autowired
private RoncooJavaMailSenderImpl javaMailSender;
public void sendMail(String email) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("email", email);
try {
String text = getTextByTemplate(template, map);
send(email, text);
} catch (IOException | TemplateException e) {
e.printStackTrace();
} catch (MessagingException e) {
e.printStackTrace();
}
}
private String getTextByTemplate(String template, Map<String, Object> model) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
return FreeMarkerTemplateUtils.processTemplateIntoString(freeMarkerConfigurer.getConfiguration().getTemplate(template), model);
}
private String send(String email, String text) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
InternetAddress from = new InternetAddress();
from.setAddress(javaMailSender.getUsername());
from.setPersonal("测试", "UTF-8");
helper.setFrom(from);
helper.setTo(email);
helper.setSubject("测试邮件");
helper.setText(text, true);
javaMailSender.send(message);
return text;
}
}
测试
flt代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div style="width: 600px; text-align: left; margin: 0 auto;">
<h1 style="color: #005da7;">测试</h1>
<div style="border-bottom: 5px solid #005da7; height: 2px; width: 100%;"></div>
<div style="border: 1px solid #005da7; font-size: 16px; line-height: 50px; padding: 20px;">
<div>${email},您好!</div>
<div>
这是个测试
</div>
<div style="border-bottom: 2px solid #005da7; height: 2px; width: 100%;"></div>
<div>扫一扫,关注测试微信公共号,里面更多精彩推荐</div>
<div>
</div>
<div>
想了解更多信息,请访问 <a href="http://www.roncoo.com">http://www.roncoo.com</a>
</div>
</div>
</div>
</body>
</html>
html代码:
<input type="text" name="email" id="email" />
<button id="send">发送邮件</button>
js代码:
$(function(){
$('#send').click(function(){
var email = $('#email').val();
$.ajax({
url:'/api/mail',
type:'post',
data:{'email':email},
success:function(msg){
alert(msg);
}
});
});
})
java代码:
@Autowired
private RoncooJavaMailComponent component;
@RequestMapping(value = "mail")
public String mail(String email) {
component.sendMail(email);
return "success";
}
session集群的解决方案:
1.扩展指定server
利用 Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略。缺点:耦合Tomcat/Jetty等Servlet容器,不能随意更换容器。
2.利用Filter
利用 HttpServletRequestWrapper,实现自己的 getSession()方法,接管创建和管理Session数据的工作。spring-session就是通过这样的思路实现的。
Spring Boot中spring session支持方式:
JDBC、MongoDB、Redis、Hazelcast、HashMap
添加依赖
<!-- spring session --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency>
配置信息
# spring session使用存储类型 #spring.session.store-type=redis # spring session刷新模式:默认on-save #spring.session.redis.flush-mode=on-save #spring.session.redis.namespace= # session超时时间,单位秒 #server.session.timeout=30 #redis #spring.redis.host=localhost #spring.redis.port=6379 #spring.redis.password=123456 #spring.redis.database=0 #spring.redis.pool.max-active=8 #spring.redis.pool.max-idle=8 #spring.redis.pool.max-wait=-1 #spring.redis.pool.min-idle=0 #spring.redis.timeout=0
测试
@RequestMapping(value = "/index")
public String index(ModelMap map, HttpSession httpSession) {
map.put("title", "第一个应用:sessionID=" + httpSession.getId());
System.out.println("sessionID=" + httpSession.getId());
return "index";
}
什么是远程调试:本地调用非本地的环境进行调试。
原理:两个VM之间通过socket协议进行通信,然后以达到远程调试的目的。
ps:如果 Java 源代码与目标应用程序不匹配,调试特性将不能正常工作。
java启动命令:
-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n 比如:java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n –jar spring-boot-demo-24-1-0.0.1-SNAPSHOT.jar
三种方式监控应用http
1. 通过 HTTP(最简单方便)
2. 通过 JMX
3. 通过远程 shell
添加依赖
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
端点(通过执行器端点可以监控应用及与应用进行交互)
1.端点暴露的方式取决于你采用的监控方式。如果使用HTTP监控,端点的ID映射到一个URL。例如,默认情况下,health端点将被映射到/health。
2.端点会默认有敏感度,根据不同的敏感度是否需要提供用户密码认证
3.如果没启用web安全,则敏感度高的会禁用
4.可以通过配置文件进行配置敏感度
5.默认情况下,除了shutdown外的所有端点都是启用的。
配置
#端点的配置 endpoints.sensitive=true endpoints.shutdown.enabled=true #保护端点 security.basic.enabled=true security.user.name=roncoo security.user.password=roncoo management.security.roles=SUPERUSER #自定义路径 security.basic.path=/manage management.context-path=/manage
备注
度量: http://localhost:8080/manage/metrics 追踪: http://localhost:8080/manage/trace
添加依赖
<!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> 版本说明: 最新 mybatis-spring-boot-starter 的版本为 1.2.0-SNAPSHOT, 依赖的是 spring boot 的 1.4.1,但是还不是 released 版本。 教程的版本为 1.1.1 依赖的 spring boot 的版本为 1.3.3.RELEASE, 兼容 spring boot 1.4.x。 GitHub: https://github.com/mybatis/spring-boot-starter
配置
#mysql spring.datasource.url=jdbc:mysql://localhost/spring_boot_demo? useUnicode=true&characterEncoding=utf-8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
创建 bean
public class RoncooUser implements Serializable {
private Integer id;
private String name;
private Date createTime;
private static final long serialVersionUID = 1L;
……
创建
mapper
@Mapper
public interface RoncooUserMapper {
@Insert(value = "insert into roncoo_user (name,
create_time) values (#{name,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP})")
int insert(RoncooUser record);
@Select(value = "select id, name, create_time from
roncoo_user where id = #{id,jdbcType=INTEGER}")
@Results(value = { @Result(column = "create_time", property
= "createTime", jdbcType = JdbcType.TIMESTAMP) })
RoncooUser selectByPrimaryKey(Integer id);
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootDemo281ApplicationTests {
@Autowired
private RoncooUserMapper mapper;
@Test
public void insert() {
RoncooUser roncooUser = new RoncooUser();
roncooUser.setName("测试");
roncooUser.setCreateTime(new Date());
int result = mapper.insert(roncooUser);
System.out.println(result);
}
@Test
public void select() {
RoncooUser result = mapper.selectByPrimaryKey(2);
System.out.println(result);
}
}
基于 mybatis xml 的集成
#mybatis mybatis.mapper-locations: classpath:mybatis/*.xml #mybatis.type-aliases-package: com.roncoo.example.bean
@Mapper
public interface RoncooUserMapper {
int insert(RoncooUser record);
RoncooUser selectByPrimaryKey(Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper
namespace="com.roncoo.example.mapper.RoncooUserMapper" >
<resultMap id="BaseResultMap"
type="com.roncoo.example.bean.RoncooUser" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="create_time" property="createTime"
jdbcType="TIMESTAMP" />
</resultMap>
<sql id="Base_Column_List" >
id, name, create_time
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap"
parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from roncoo_user
where id = #{id,jdbcType=INTEGER}
</select>
<insert id="insert"
parameterType="com.roncoo.example.bean.RoncooUser" >
insert into roncoo_user (id, name, create_time)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP})
</insert>
</mapper>
如何快速批量生成 bean, mapper, xml? 使用 mybatis generator, 龙果开源了 roncoo-mybatis-generator, 集成了多个插件 GitHub: https://github.com/roncoo/roncoo-mybatis-generator
Druid是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。
添加依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.26</version> </dependency>
配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource spring.datasource.url=jdbc:mysql://localhost/spring_boot_demo?useUnicode=true&characterEncod ing=utf-8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver 注意: 关于 spring.datasource.type 的说明 旧版本不支持这个属性, 1.3.x 开始支持,但是 1.4.0 不支持, 1.4.1 重新支持。
添加 druid 的支持类
@Configuration
public class DruidConfiguration {
@ConditionalOnClass(DruidDataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue =
"com.alibaba.druid.pool.DruidDataSource", matchIfMissing = true)
static class Druid extends DruidConfiguration {
@Bean
@ConfigurationProperties("spring.datasource.druid")
public DruidDataSource dataSource(DataSourceProperties properties) {
DruidDataSource druidDataSource = (DruidDataSource)
properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
DatabaseDriver databaseDriver =
DatabaseDriver.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
druidDataSource.setValidationQuery(validationQuery);
}
return druidDataSource;
}
}
}
配置 servlet
@WebServlet(urlPatterns = { "/druid/*" }, initParams =
{ @WebInitParam(name = "loginUsername", value = "roncoo"),
@WebInitParam(name = "loginPassword", value = "roncoo") })
public class DruidStatViewServlet extends StatViewServlet {
private static final long serialVersionUID = 1L;
}
配置 filter
@WebFilter(filterName = "druidWebStatFilter", urlPatterns =
"/*", initParams = { @WebInitParam(name = "exclusions", value
= "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*") })
public class DruidWebStatFilter extends WebStatFilter {
} #
初始化连接大小
spring.datasource.druid.initial-size=8
#最小空闲连接数
spring.datasource.druid.min-idle=5
#最大连接数
spring.datasource.druid.max-active=10
#查询超时时间
spring.datasource.druid.query-timeout=6000
#事务查询超时时间
spring.datasource.druid.transaction-query-timeout=6000
#关闭空闲连接超时时间
spring.datasource.druid.remove-abandoned-timeout=1800
测试: http://localhost:8080/druid/index.html
sql 监控配置 #filter类名:stat,config,encoding,logging spring.datasource.druid.filters=stat
spring 监控配置
@ImportResource(locations = { "classpath:druid-bean.xml" })
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
http://swagger.io/
Springfox 的前身是 swagger-springmvc,是一个开源的 API doc 框架,可以将我们的 Controller 的
方法以文档的形式展现,基于 Swagger。
http://springfox.github.io/springfox/添加jar
<!-- Swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.0</version> </dependency>
配置
/**
* SwaggerConfig
*/
@Configuration
@EnableSwagger2
public class Swagger2Configuration {
/**
*
* @return
*/
@Bean
public Docket accessToken() {
return new Docket(DocumentationType.SWAGGER_2).groupName("api")// 定义组
.select() // 选择那些路径和 api 会生成 document
.apis(RequestHandlerSelectors.basePackage("com.roncoo.example.controller")) // 拦截的包
路径
.paths(regex("/api/.*"))// 拦截的接口路径
.build() // 创建
.apiInfo(apiInfo()); // 配置说明
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()//
.title("龙果学院")// 标题
.description("spring boot 全集")// 描述
.termsOfServiceUrl("http://www.roncoo.com")//
.contact(new Contact("wujing", "http://www.roncoo.com",
"297115770@qq.com"))// 联系
// .license("Apache License Version 2.0")// 开源协议
// .licenseUrl("https://github.com/springfox/springfox/blob/master/LICENSE")// 地址
.version("1.0")// 版本
.build();
}
}
测试
http://localhost:8080/swagger-ui.html
自定义(注解的使用)
@ApiIgnore 忽略暴露的 api @ApiOperation(value = "查找", notes = "根据用户 ID 查找用户") 添加说明 其他注解: @Api: 用在类上,说明该类的作用 @ApiImplicitParams: 用在方法上包含一组参数说明 @ApiResponses: 用于表示一组响应 @ApiResponse: 用在@ApiResponses 中,一般用于表达一个错误的响应信息 code:数字,例如 400 message:信息,例如"请求参数没填好" response:抛出异常的类 @ApiModel: 描述一个 Model 的信息(这种一般用在 post 创建的时候,使用@RequestBody 这样的场 景,请求参数无法使用@ApiImplicitParam 注解进行描述的时候) @ApiModelProperty: 描述一个 model 的属性
我们在编写Spring Boot应用中经常会遇到这样的场景,比如:我需要定时地发送一些短信、邮件之类的操作,也可能会定时地检查和监控一些标志、参数等。
创建定时任务
在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务,实现每过5秒输出一下当前时间。
在Spring Boot的主类中加入@EnableScheduling注解,启用定时任务的配置
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
创建定时任务实现类
@Component
public class ScheduledTasks {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
System.out.println("现在时间:" + dateFormat.format(new Date()));
}
}
运行程序,控制台中可以看到类似如下输出,定时任务开始正常运作了。
现在时间:10:40:09 现在时间:10:40:14 现在时间:10:40:19 现在时间:10:40:24 现在时间:10:40:29522 现在时间:10:40:34
@Scheduled详解 在上面的入门例子中,使用了@Scheduled(fixedRate = 5000) 注解来定义每过5秒执行的任务,对于@Scheduled的使用可以总结如下几种方式: @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行 @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行 @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次 @Scheduled(cron="*/5 * * * * *") :通过cron表达式定义规则
什么是“异步调用”?
“异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。
下面通过一个简单示例来直观的理解什么是同步调用:
定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)
@Component
public class Task {
public static Random random =new Random();
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
在单元测试用例中,注入Task对象,并在测试用例中执行doTaskOne、doTaskTwo、doTaskThree三个函数。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}
执行单元测试,可以看到类似如下输出:
开始做任务一 完成任务一,耗时:4256毫秒 开始做任务二 完成任务二,耗时:4957毫秒 开始做任务三 完成任务三,耗时:7173毫秒
任务一、任务二、任务三顺序的执行完了,换言之doTaskOne、doTaskTwo、doTaskThree三个函数顺序的执行完成。
上述的同步调用虽然顺利的执行完了三个任务,但是可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行。
在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,Task类改在为如下模式:
@Component
public class Task {
@Async
public void doTaskOne() throws Exception {
// 同上内容,省略
}
@Async
public void doTaskTwo() throws Exception {
// 同上内容,省略
}
@Async
public void doTaskThree() throws Exception {
// 同上内容,省略
}
}
为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync,如下所示:
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如:
没有任何任务相关的输出 有部分任务相关的输出 乱序的任务相关的输出
原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。
注: @Async所修饰的函数不要定义为static类型,这样异步调用不会生效
为了让doTaskOne、doTaskTwo、doTaskThree能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。
那么我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future<T>来返回异步调用的结果,就像如下方式改造doTaskOne函数:
@Async
public Future<String> doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务一完成");
}
按照如上方式改造一下其他两个异步函数之后,下面我们改造一下测试用例,让测试在等待完成三个异步调用之后来做一些其他事情。
@Test
public void test() throws Exception {
long start = System.currentTimeMillis();
Future<String> task1 = task.doTaskOne();
Future<String> task2 = task.doTaskTwo();
Future<String> task3 = task.doTaskThree();
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// 三个任务都调用完成,退出循环等待
break;
}
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
看看我们做了哪些改变:
在测试用例一开始记录开始时间
在调用三个异步函数的时候,返回Future<String>类型的结果对象
在调用完三个异步函数之后,开启一个循环,根据返回的Future<String>对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。
执行一下上述的单元测试,可以看到如下结果:
开始做任务一 开始做任务二 开始做任务三 完成任务三,耗时:37毫秒 完成任务二,耗时:3661毫秒 完成任务一,耗时:7149毫秒 任务全部完成,总耗时:8025毫秒
可以看到,通过异步调用,让任务一、二、三并发执行,有效的减少了程序的总运行时间。
Spring Security进行安全控制
在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面。要实现访问控制的方法多种多样,可以通过Aop、拦截器实现,也可以通过框架实现(如:Apache Shiro、Spring Security)。
Web层实现请求映射
@Controller
public class HelloController {
@RequestMapping("/")
public String index() {
return "index";
}
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
/:映射到index.html
/hello:映射到hello.html
实现映射的页面
src/main/resources/templates/index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security入门</title>
</head>
<body>
<h1>欢迎使用Spring Security!</h1>
<p>点击 <a th:href="@{/hello}">这里</a> 打个招呼吧</p>
</body>
</html>
src/main/resources/templates/hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
可以看到在index.html中提供到/hello的链接,显然在这里没有任何安全控制,所以点击链接后就可以直接跳转到hello.html页面。
整合Spring Security
在这一节,我们将对/hello页面进行权限控制,必须是授权用户才能访问。当没有权限的用户访问后,跳转到登录页面。
添加依赖
在pom.xml中添加如下配置,引入对Spring Security的依赖。
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
</dependencies>
Spring Security配置
创建Spring Security的配置类WebSecurityConfig,具体如下:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
通过@EnableWebSecurity注解开启Spring Security的功能 继承WebSecurityConfigurerAdapter,并重写它的方法来设置一些web安全的细节 configure(HttpSecurity http)方法 通过authorizeRequests()定义哪些URL需要被保护、哪些不需要被保护。例如以上代码指定了/和/home不需要任何认证就可以访问,其他的路径都必须通过身份验证。 通过formLogin()定义当需要用户登录时候,转到的登录页面。 configureGlobal(AuthenticationManagerBuilder auth)方
新增登录请求与页面
在完成了Spring Security配置之后,我们还缺少登录的相关内容。
HelloController中新增/login请求映射至login.html
@Controller
public class HelloController {
// 省略之前的内容...
@RequestMapping("/login")
public String login() {
return "login";
}
}
新增登录页面:src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
用户名或密码错
</div>
<div th:if="${param.logout}">
您已注销成功
</div>
<form th:action="@{/login}" method="post">
<div><label> 用户名 : <input type="text" name="username"/> </label></div>
<div><label> 密 码 : <input type="password" name="password"/> </label></div>
<div><input type="submit" value="登录"/></div>
</form>
</body>
</html>
可以看到,实现了一个简单的通过用户名和密码提交到/login的登录方式。
根据配置,Spring Security提供了一个过滤器来拦截请求并验证用户身份。如果用户身份认证失败,页面就重定向到/login?error,并且页面中会展现相应的错误信息。若用户想要注销登录,可以通过访问/login?logout请求,在完成注销之后,页面展现相应的成功消息。
到这里,我们启用应用,并访问http://localhost:8080/,可以正常访问。但是访问http://localhost:8080/hello的时候被重定向到了http://localhost:8080/login页面,因为没有登录,用户没有访问权限,通过输入用户名user和密码password进行登录后,跳转到了Hello World页面,再也通过访问http://localhost:8080/login?logout,就可以完成注销操作。
为了让整个过程更完成,我们可以修改hello.html,让它输出一些内容,并提供“注销”的链接。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
</body>
</html>
注意:
1. 去除不需要的 jar 开发工具 jar: spring-boot-devtools 2. 监控一定要做好权限控制或者去除 控制 jar: spring-boot-starter-actuator druid 的监控 swagger 的接口 3、 打包, 跳过测试 maven: clean package -Dmaven.test.skip=true
脚本:
#!/bin/sh
## chang here
SERVICE_DIR=/roncoo/spring-boot-demo
SERVICE_NAME=spring-boot-demo-31-1-0.0.1-SNAPSHOT
SPRING_PROFILES_ACTIVE=dev
## java env
export JAVA_HOME=/opt/jdk1.7.0_79
export JRE_HOME=${JAVA_HOME}/jre
case "$1" in
start)
procedure=`ps -ef | grep -w "${SERVICE_NAME}" |grep -w "java"| grep -v "grep" | awk '{print $2}'`
if [ "${procedure}" = "" ];
then
echo "start ..."
if [ "$2" != "" ];
then
SPRING_PROFILES_ACTIVE=$2
fi
echo "spring.profiles.active=${SPRING_PROFILES_ACTIVE}"
exec nohup ${JRE_HOME}/bin/java -Xms128m -Xmx512m -jar ${SERVICE_DIR}/${SERVICE_NAME}/.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE} >/dev/null 2>&1 &
echo "start success"
else
echo "${SERVICE_NAME} is start"
fi
;;
stop)
procedure=`ps -ef | grep -w "${SERVICE_NAME}" |grep -w "java"| grep -v "grep" | awk '{print $2}'`
if [ "${procedure}" = "" ];
then
echo "${SERVICE_NAME} is stop"
else
kill -9 ${procedure}
sleep 1
argprocedure=`ps -ef | grep -w "${SERVICE_NAME}" |grep -w "java"| grep -v "grep" | awk '{print $2}'`
if [ "${argprocedure}" = "" ];
then
echo "${SERVICE_NAME} stop success"
else
kill -9 ${argprocedure}
echo "${SERVICE_NAME} stop error"
fi
fi
;;
restart)
$0 stop
sleep 1
$0 start $2
;;
*)
echo "usage: $0 [start|stop|restart] [dev|test|prod]"
;;
esac
1.使用基于spring boot的配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/base.xml"/> <logger name="org.springframework.web" level="DEBUG"/> </configuration>
log4j配置(去除logback的依赖包,添加log4j2的依赖包)
<exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions>
<!-- 使用log4j2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>