目录
1.2 @ResponseBody 和 @RequestBody
1.3 @RestController:Api获取的 JSON 数据自动转换
7.1 使用 @PropertySource 和 @Value 映射属性
十、过滤器 Filter、自定义Servlet、监听器Listener
11.2 Mybatis 的使用 -- 使用 java 方式
12.3 可视化工具 Redis Desktop Manager
一、注解
1.1 @RequestMapping: 配置URL映射
@RequestMapping 用于定义Api接口和逻辑方法的映射关系
@RestController
@RequestMapping("/home ") // 配置在类上
public class IndexController {
@RequestMapping(value = {
" ",
"/page ",
"page* ",
"view/*,**/msg "
})
String indexMultipleMapping() {
return "Hello from index multiple mapping. ";
}
@RequestMapping(value="/hello",method= RequestMethod.GET) // 配置在处理器上
public String sayHello(){
return "hello";
}
}
1.2 @ResponseBody 和 @RequestBody
@ResponseBody 的作用是将 controller 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到 response 对象的body区,通常用来返回JSON数据或者是 XML。需要配合 @Controller 使用
@RequestBody 用于将 Controller 的方法参数,根据 HTTP Request Header 的 content-Type
的内容,将其转换成 Bean 对象。
通常用来处理数据类型为 application/json 或者是 application/xml 的内容
@RequestMapping(value = "/testRequestBody", method= RequestMethod.POST)
@ResponseBody
public Person testRequestBody(@RequestBody Person p) {
System.out.println("creating a employee:" + p);
return p;
}
1.3 @RestController:Api获取的 JSON 数据自动转换
@RestController是 springboot4.0 新加入的注解,原来返回 JSON 需要 @ResponseBody 和 @Controller 配合,但现在只需要使用 @RestController 就可以。即 @RestController = @ResponseBody + @Controller。同时不受 @Controller 需要模板配合的限制
@SpringBootApplication
@RestController
public class HelloSpringApplication {
public static void main(String[] args) {
SpringApplication.run(HelloSpringApplication.class, args);
}
/*
* 使用bean对象传参
* 使用body传输数据
*/
@RequestMapping("/v2/save_user")
public Object save_user(@RequestBody User user) {
params.clear();
params.put("user", user);
return params;
}
}
1.4 @Controller
@Controller 用于标记在一个类上,将它标记的类成为一个 SpringMVC Controller 对象。分发处理器会扫描使用了该注解的类的方法,并在符合条件时执行相应的动作。
@Controller
public class SampleController {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World";
}
@RequestMapping("/test")
public Map<String,String> testMap() {
Map<String, String> map = new HashMap<>();
map.put("name", "xiaozhang");
map.put("age", "18");
return map;
}
}
1.5 @Autowired
@Autowired 是用于自动装配参数,避免使用 setter 参数来赋值。通常用户赋予配置文件的变量到配置实体类中。
二、http请求相关
2.1 Get方式请求
@RestController
public class GetController {
private Map<String, Object> params = new HashMap<>();
@GetMapping(path = "/{city_id}/{user_id}")
public Object findUser(@PathVariable("city_id") String cityId, @PathVariable("user_id") String userId) {
params.clear();
params.put("cityId", cityId);
params.put("userId", userId);
return params;
}
@GetMapping(path = "/v1/page_user")
public Object findUser(int from, int to) {
params.clear();
params.put("from", from);
params.put("to", to);
return params;
}
@GetMapping(path = "/v2/page_user")
public Object page_from(@RequestParam(defaultValue = "0", name="key") int from, int to) {
params.clear();
params.put("from", from);
params.put("to", to);
return params;
}
@GetMapping("/v2/get_header")
public Object save_user(@RequestHeader("access_token") String accessToken, String id) {
params.clear();
params.put("accessToken", accessToken);
params.put("id", id);
return params;
}
// 使用servlet方式获取参数
@GetMapping("/v2/test_header")
public Object test_user(HttpServletRequest request) {
params.clear();
String id = request.getParameter("id");
params.put("id", id);
return params;
}
}
2.2 POST方式请求
主要使用有 POST(提交),PUT(更新), DELETE(删除)。
@RestController
public class OtherHttpController {
private Map<String,Object> params = new HashMap<>();
// 提交
@PostMapping("/post/testUser")
public Object test_user_post(@RequestBody User user) {
params.clear();
params.put("user", user);
return params;
}
// 更新
@PutMapping("/put/testUser")
public Object test_user_put(@RequestBody User user) {
params.clear();
params.put("user", user);
return params;
}
// 删除
@DeleteMapping("/del/testUser")
public Object test_user_del(@RequestBody User user) {
params.clear();
params.put("user", user);
return params;
}
}
三、Jackson 处理
指定字段不返回: @JsonIgnore
指定日期格式: @JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",local="zh",timezone="GMT+8")
空字段不返回:@JsonInclude(Include.NON_null)
指定别名:@JsonProperty
四、同个文件加载顺序
4.1 加载顺序
对同个文件的加载顺序,springboot 会挨个查找:
META/resources > resouces > static > public,查找是否存在相应的资源,如果有则直接返回
4.2 自定义加载文件夹和加载顺序
在 application.properties 文件中配置:
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
4.3 直接url访问html访问不了
明明有这个html,直接尝试访问html会报错
这是因为没有存放在默认加载的文件夹下(resources、public、static),需要使用模板引擎:
1)在pom.xml文件中,引入 thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2)使用接口方式返回页面
@Controller
public class fileController {
@RequestMapping("/api/v1/goindex")
public Object index() {
return "index";
}
}
五、使用MultipartFile实现文件上传
5.1 实现上传代码
@RestController
public class fileController {
private static final String filePath = "E:/study/Java/hello-spring/src/main/resources/static/images/";
@RequestMapping("/upload")
public ResponseData upload(@RequestParam("head_img")MultipartFile file, HttpServletRequest request) {
String name = request.getParameter("name");
System.out.println("用户名: " + name);
// 获取文件名
String fileName = file.getOriginalFilename();
System.out.println("上传的文件名为: " + fileName);
// 获取文件名的后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));
System.out.println("上传的后缀名为: " + suffixName);
// 获取文件上传的路径
fileName = UUID.randomUUID() + suffixName;
System.out.println("转换后的名称为: " + fileName);
File dest = new File(filePath + fileName);
try {
file.transferTo(dest);
return new ResponseData(0, fileName);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseData(-1, null, "上传失败");
}
}
package tom.spring.hellospring.domain;
import java.io.Serializable;
public class ResponseData implements Serializable {
private static final long serialVersionUID = 1L;
// 状态码 0 成功 -1 失败
private int code;
// 结果
private Object data;
// 消息
private String msg;
public ResponseData(int code, Object data) {
this.code = code;
this.data = data;
}
public ResponseData(int code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
5.2 实现上传大小限制
在项目入口文件中添加:
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
// 设置单个文件最大
factory.setMaxFileSize(DataSize.parse("1024KB"));
// 设置总上传数据总大小
factory.setMaxRequestSize(DataSize.parse("10240KB"));
return factory.createMultipartConfig();
}
5.3 部署成jar包,并指定上传目录
5.3.1 增加 jar 包的 Maven 依赖
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
5.3.2 增加配置文件,指定上传路径
在 application.properties 中增加:
web.upload-path=E:/study/Java/upload
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:{web.upload-path}/
修改 fileController.java 文件:
private static final String filePath = "E:/study/Java/upload/";
打包成 jar 包,并运行:
// 使用 maven install
// 生成 jar 包后,运行 jar 包
java -jar xxxxx.jar
5.4 性能优化
若是要快速开发,可以使用nginx来配置文件服务器。若要有更好的性能,可以使用 fastdfs 或 oss 来进行搭建。
六、打包后,使用分离的配置文件
在实际环境中,可能需要修改配置文件,如果将配置文件打包在项目代码里,则每次修改配置文件都要重新上传项目,这无疑是很麻烦和易错的。
可以将配置文件和代码分离开,实现直接修改配置文件,重启项目即可。只需要在运行 java -jar 时,加入
-Dspring.config.location=/usr/local/proj/application.properties
修改完毕后重启即可。
# 获取当前运行路径
CRTDIR=$(pwd)
PRONAME="upload-demo.jar"
# 判断是否有upload文件夹,没有则创建,用于存储图片
if [ ! -d "${CRTDIR}/upload" ]; then
mkdir ${CRTDIR}/upload
fi
# 判断是否有logs文件夹,没有则创建,用于存储日志
if [ ! -d "${CRTDIR}/logs" ]; then
mkdir ${CRTDIR}/logs
fi
# 判断logs文件夹下是否有upload.log文件,没有则创建,用于记录项目日志
if [ ! -f "${CRTDIR}/logs/upload.log" ]; then
touch ${CRTDIR}/logs/upload.log
fi
nohup java -jar -Dspring.config.location=${CRTDIR}/application.properties ${CRTDIR}/${PRONAME} > ${CRTDIR}/logs/upload.log &
tail -f ${CRTDIR}/logs/upload.log
七、注解自动映射到属性和实体类
7.1 使用 @PropertySource 和 @Value 映射属性
在需要映射属性的类上方增加注解,使用 @PropertySource 指定配置文件名,使用 @Value 映射到具体的属性上
@RestController
@PropertySource({"classpath:application.properties"})
public class fileController {
@Value("${web.file.path}")
private String filePath;
.....
}
7.2 通过实体类配置
可以通过 @PropertySource 来指定配置文件,然后通过 @ConfigurationProperties来加载指定前缀的参数,并自动匹配到类的属性中去。
使用样例:
在 application.properties 文件中存在如下配置:
# 测试配置文件注入实体类
test.domain=www.greattom.xyz:8080
test.name=springboot-project
创建配置实体类ServerSettings:
package tom.spring.hellospring.domain;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource({"classpath:application.properties"})
@ConfigurationProperties(prefix="test")
public class ServerSettings {
private String name;
private String domain;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
}
这样配置文件中的 name 属性和 domain 属性就自动匹配到属性中去了。
在控制器中使用:
@RestController
public class GetController {
....
@Autowired
private ServerSettings serverSettings;
@GetMapping("v1/test_properties")
public Object testProperties() {
return serverSettings;
}
}
八、全局异常
使用 @RestControllerAdvice 捕获全局异常,并用 @ExceptionHandler 指定异常的处理方法。
@RestControllerAdvice
public class CustomEtHandler {
// 捕获全局异常
@ExceptionHandler(value=Exception.class)
Object handlerException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("code", 100);
map.put("msg", e.getMessage());
map.put("url", request.getRequestURL());
return map;
}
// 捕获自定义异常
@ExceptionHandler(value=MyException.class)
Object handleMyException(MyException e, HttpServletRequest request) {
// 返回指定界面
// ModelAndView modelAndView = new ModelAndView();
// modelAndView.setViewName("error.html");
// modelAndView.addObject("msg", e.getMessage());
// return modelAndView;
Map<String, Object> map = new HashMap<>();
map.put("code", e.getCode());
map.put("msg", e.getMsg());
map.put("url", request.getRequestURL());
return map;
}
}
若是返回指定界面,则需要加入 Thymekeaf 的 maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
九、将项目打包成 war 包,并在 tomcat 运行
9.1 打包
1) 指定打包方式
<packaging>war</packaging>
2)指定项目名称,名称可自定义
<finalName>xxxx项目名称</finalName>
3)修改启动类,使用 SpringBootServletInitializer 实现初始化
public class HelloSpringApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(HelloSpringApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(HelloSpringApplication.class, args);
}
}
4)maven install 生成 war 包
9.2 tomcat 下运行
将 war 包放置到 tomcat 的 webapp 文件夹下,启动 tomcat
十、过滤器 Filter、自定义Servlet、监听器Listener
10.1 Filter
通过 @WebFilter 来定义这个类为过滤器,并使用 urlPatterns 来指定过滤器的管控路径,使用 filterName 定义过滤器名称。
@WebFilter(urlPatterns = "/api/*", filterName = "loginFilter")
public class LoginFilter implements Filter {
/*
* 容器调用的时候
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init loginFilter");
}
/*
* 容器被销毁时调用
*/
@Override
public void destroy() {
System.out.println("Destory loginFilter");
}
/*
* 请求拦截的时候调用
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter loginFilter");
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
String username = req.getParameter("username");
if ("tom".equals(username)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
resp.sendRedirect("/index.html");
return;
}
}
}
同时,在启动类中增加:@ServletComponentScan,这个注解会扫描所有的 @WebFilter、@WebListener、@WebListener 并自动注册。
10.2 自定义 Servlet
Servlet 可以认为是接口,但在框架中使用 @RequestMapping 等注解则是自动帮我们完成了编写和注册 Servlet 的操作。故使用框架更为方便。
@WebServlet(name = "userServlet", urlPatterns = "/v1/test/customs")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("custom sevlet");
resp.getWriter().flush();
resp.getWriter().close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
10.3 监听器 Listener
通过 @WebListener 来注册监听器
@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("========== RequestDestoryed =========");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("========== RequestInit =========");
}
}
10.4 拦截器
拦截器需要编写配置类,并使用 @Component 注解来让编译器扫描它。多个拦截器时,拦截器执行顺序会根据注册顺序拦截。
// 注册类
@Configuration
public class CustomWebMvcConf implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 调用时依照注册顺序调用
registry.addInterceptor(new LoginIntercepter()).addPathPatterns("/api2/*/**");
registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api2/**");
// .registry.excludePathPatterns("/api2/register") // 排除注册的路径
WebMvcConfigurer.super.addInterceptors(registry);
}
}
// 拦截器一
public class LoginIntercepter implements HandlerInterceptor {
/*
* 调用方法之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginIntercepter preHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
/*
* 调用方法之后,视图渲染之前
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("LoginIntercepter postHandle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/*
* 方法调用完成之后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("LoginIntercepter afterHandle");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
// 拦截器二
public class TwoIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("TwoInterceptor preHander");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("TwoInterceptor postHander");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("TwoInterceptor afterHander");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
10.4 调用顺序
可以看到,调用顺序为: 过滤器 => 拦截器 => 逻辑代码 => 拦截器
10.5 过滤器和拦截器的区别和使用
过滤器 Filter | 拦截器 Interceptor | |
实现原理 | 基于函数回调 doFilter() | 基于 AOP 思想(Java 反射) |
生命周期 | 只在函数调用前后起作用 | 方法前后,异常抛出前后等 |
依赖环境 | 依赖于 Servlet 容器中 | 不依赖 Servlet 容器 |
调用次数 | 整个 Action 中只能被调用一次 | 可以被多次调用 |
触发范围 | 可以拦截几乎所有请求(Action 请求和 静态资源请求) | 只能拦截 Action 请求 |
调用顺序为:
过滤前-拦截前-action执行-拦截后-过滤后
十一、MyBatis 使用
11.1 什么是 Mybatis
MyBatis 是用于映射 java 实体类和数据库的,基于JAVA的半ORM持久层框架。
11.2 Mybatis 的使用 -- 使用 java 方式
11.2.1 在启动入口类上添加mapper处理路径
@MapperScan("tom.spring.hellospring.mapper")
11.2 增加配置文件
#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.47.135:3306/mybatisDemo?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =Tom@123456
#如果不使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
11.3 构建项目目录
11.4 增加文件
// UserService.java
/*
* service 层定义接口,具体实现由impl完成
*/
public interface UserService {
public Object addUser(UserEntity userEntity);
public Object delUser(int id);
public Object updateUser(UserEntity userEntity);
public Object findById(int id);
public List<UserEntity> getAll();
}
// UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public Object addUser(UserEntity userEntity) {
userMapper.save(userEntity);
return JsonData.buildSuccess(userEntity);
}
@Override
public Object delUser(int id) {
userMapper.remove(id);
return JsonData.buildSuccess();
}
@Override
public Object updateUser(UserEntity userEntity) {
userMapper.update(userEntity);
return JsonData.buildSuccess();
}
@Override
public UserEntity findById(int id) {
return userMapper.findById(id);
}
@Override
public List<UserEntity> getAll() {
return userMapper.getAll();
}
}
// UserMapper.java
@Mapper
@Repository
public interface UserMapper extends BasicMapper<UserEntity> {
@Override
@Insert("INSERT INTO user(name, phone, create_time, age) VALUES(#{name}, #{phone}, #{createTime}, #{age})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
int save(UserEntity userEntity);
@Override
@Update("UPDATE user SET name=#{name} WHERE id=#{id}")
int update(UserEntity userEntity);
@Override
@Delete("DELETE FROM user WHERE id=#{id}")
int remove(Object id);
@Override
@Select("SELECT * FROM user WHERE id=#{id}")
@Results(
@Result(column = "create_time", property = "createTime")
)
UserEntity findById(Object id);
@Override
@Select("SELECT * FROM user")
@Results(
@Result(column = "create_time", property = "createTime")
)
List<UserEntity> getAll();
}
11.3 Mybatis 的使用 -- 使用 xml 方式
11.3.1 增加项目文件
// StudentMapper.java
@Mapper
@Repository
public interface StudentMapper extends BasicMapper<StudentEntity> {
}
// StudentMapper.xml
<?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="tom.spring.hellospring.mapper.StudentMapper">
<!-- <resultMap type="tom.spring.hellospring.entity.StudentEntity" id="StudentEntityResult">-->
<!-- <id property="id" column="id" />-->
<!-- <result property="createTime" column="create_time" />-->
<!-- </resultMap>-->
<insert id="save" useGeneratedKeys="true" keyProperty="id" keyColumn="id" >
INSERT INTO student (
`name`,
`phone`,
`create_time`,
`age`
)
VALUES (
#{name},
#{phone},
#{create_time},
#{age}
)
</insert>
<delete id="remove">
DELETE FROM
student
WHERE
id = #{id}
</delete>
<select id="findById" resultType="tom.spring.hellospring.entity.StudentEntity">
SELECT
*
FROM
student
WHERE
id = #{id}
</select>
<select id="getAll" resultType="tom.spring.hellospring.entity.StudentEntity">
SELECT
*
FROM
student
</select>
</mapper>
// StudentService.java
public interface StudentService {
public Object addStu(StudentEntity stu);
public List<StudentEntity> getAll();
}
// StudentServiceImpl.java
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
public Object addStu(StudentEntity stu) {
studentMapper.save(stu);
return JsonData.buildSuccess(stu);
}
public List<StudentEntity> getAll() {
return studentMapper.getAll();
}
}
// StudentEntity.java
public class StudentEntity {
private int id;
private String name;
private String phone;
public int age;
private Date create_time;
public StudentEntity() {
}
public StudentEntity(int id, String name, String phone, int age, Date create_time) {
this.id = id;
this.name = name;
this.phone = phone;
this.age = age;
this.create_time = create_time;
}
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 String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getCreate_time() {
return create_time;
}
public void setCreate_time(Date create_time) {
this.create_time = create_time;
}
}
// StudentController.java
@RestController
@RequestMapping("/stu")
public class StudentController {
@Autowired
StudentService stu;
@GetMapping("add")
public Object add_stu() {
StudentEntity stuEnty = new StudentEntity();
stuEnty.setName("xiaoming");
stuEnty.setAge(18);
stuEnty.setCreate_time(new Date());
stuEnty.setPhone("10099");
return stu.addStu(stuEnty);
}
@GetMapping("getAll")
public Object get_all() {
return stu.getAll();
}
}
11.3.2 修改配置文件
在 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
......
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
在 application.properties 中增加:
mapper-locations 指向放置 xml 文件的路径
type-aliases-package 指向实体类的文件夹
spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://1.1.1.1:3306/mybatisDemo?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =xxxx
spring.datasource.password =xxxx
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
mybatis.mapper-locations:classpath:tom/spring/hellospring/mapper/*.xml
mybatis.type-aliases-package=tom.spring.hellospring.entity
11.4 Mybatis 的使用 -- 事务
在 impl 类中的实现方法中使用注解 @Transactional
@Override
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
// isolation 隔离级别 propagation 传播行为
public int addCount(UserEntity userEntity) {
userMapper.save(userEntity);
// 模拟异常
int i = 19/0;
return 0;
}
十二、Redis 使用
12.1 安装和配置
12.1.1 安装
网盘下载:
链接:https://pan.baidu.com/s/1mingLTtX0hmIWeOd2-dOQw
提取码:tkmu
官网下载:
在 /usr/local 目录下:
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar xzf redis-5.0.7.tar.gz
mv redis-5.0.7 redis
cd redis
make
测试是否成功:
./src/redis-server
12.1.2 设置后台启动
将 redis.conf 文件中的 daemonize 从 no 改为 yes,重启 redis
12.1.3 配置脚本:开启和关闭
脚本 - 启动 redis:start.sh
#! /bin/bash
/usr/local/redis/src/redis-server /usr/local/redis/redis.conf
脚本 - 关闭 redis: stop.sh
#! /bin/bash
/usr/local/redis/src/redis-cli shutdown
脚本 - 进入客户端: client.sh
#! /bin/bash
./src/redis-cli
12.1.4 设置远程访问权限(正式环境记得关掉)
将 redis.conf 文件中的 bind 字段注释掉,或将 bind 127.0.0.1 改为 bind 0.0.0.0
将 protected-mode 从 yes 改为 no
protected-mode 当为yes时,禁止外网访问,生产环境应设为yes
12.1.5 修改默认端口6379
将 redis.conf 文件中的 port 6379 改为要分配的端口
12.1.6 设置和修改密码
redis 初始时是没有密码的,为了安全性需要对 redis 设置密码
进入 redis 后,配置密码:
config set requirepass 123456
查看:info(验证无法通过)
授权登陆 auth 123456
12.2 使用
在 https://docs.spring.io/spring-boot/docs/2.2.4.RELEASE/reference/htmlsingle/ 中可以看到maven:
在 pom.xml 中引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
使用:
@RestController
@RequestMapping("/redis")
public class RedisController {
@Autowired
private StringRedisTemplate redisTpl;
@GetMapping(value = "get")
public Object get() {
String value = redisTpl.opsForValue().get("name");
return JsonData.buildSuccess(value);
}
@GetMapping(value = "add")
public Object add() {
redisTpl.opsForValue().set("name", "tom");
return JsonData.buildSuccess("ok");
}
}
12.3 可视化工具 Redis Desktop Manager
网盘链接:https://pan.baidu.com/s/1T8_HbJ3GLRDw9A2YtOgdoQ 提取码:juhw
12.4. 封装
redisClient.java :
@Component
public class RedisClient {
@Autowired
private StringRedisTemplate redisTpl;
public boolean set(String key, String value) {
try {
redisTpl.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public String get(String key) {
return key==null?null:redisTpl.opsForValue().get(key);
}
public boolean remove(String key) {
return key==null?null:redisTpl.delete(key);
}
/**
* 功能描述:设置某个key过期时间
* @param key
* @param time
* @return
*/
public boolean expire(String key,long time){
try {
if(time>0){
redisTpl.expire(key, time, TimeUnit.SECONDS);
// redisTpl.expire(key, time, TimeUnit.MILLISECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 功能描述:根据key 获取过期时间
* @param key
* @return
*/
public long getExpire(String key){
return redisTpl.getExpire(key,TimeUnit.SECONDS);
}
/**
* 递增
* @param key 键
* @return
*/
public long incr(String key, long delta){
return redisTpl.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几
* @return
*/
public long decr(String key, long delta){
return redisTpl.opsForValue().increment(key, -delta);
}
}
工具类 JsonUtils.java :
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
//对象转字符串
public static <T> String obj2String(T obj){
if (obj == null){
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//字符串转对象
public static <T> T string2Obj(String str,Class<T> clazz){
if (StringUtils.isEmpty(str) || clazz == null){
return null;
}
try {
return clazz.equals(String.class)? (T) str :objectMapper.readValue(str,clazz);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
测试 RedisController.java :
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("add")
public Object addUser() {
UserEntity userEntity = new UserEntity();
userEntity.setName("xiaoming");
userEntity.setPhone("10086");
userEntity.setAge(18);
userEntity.setCreateTime(new Date());
userService.addUser(userEntity);
return JsonData.buildSuccess(userEntity);
}
@GetMapping("del")
public Object delUser(int id) {
userService.delUser(id);
return JsonData.buildSuccess();
}
@GetMapping("update")
public Object updateUser(String name, int id) {
UserEntity userEntity = new UserEntity();
userEntity.setName(name);
userEntity.setId(id);
userService.updateUser(userEntity);
return JsonData.buildSuccess();
}
@GetMapping("findById")
public Object findById(int id) {
return JsonData.buildSuccess(userService.findById(id));
}
@GetMapping("getAll")
public Object getAll() {
return JsonData.buildSuccess(userService.getAll());
}
@GetMapping("addCount")
public Object addCount() {
UserEntity userEntity = new UserEntity();
userEntity.setName("laowang");
userEntity.setPhone("111");
userEntity.setAge(29);
userEntity.setCreateTime(new Date());
userService.addCount(userEntity);
return JsonData.buildSuccess(userEntity);
}
}
十三、Schedule 定时任务
使用定时任务,例如每天凌晨统计数据,每月统计数据等。
13.1 几种定时任务方式
1、常见定时任务 Java自带的java.util.Timer类
timer:配置比较麻烦,时间延后问题
timertask:不推荐
2、Quartz框架
配置更简单
xml或者注解
3、SpringBoot使用注解方式开启定时任务
1)启动类里面 @EnableScheduling开启定时任务,自动扫描
2)定时任务业务类 加注解 @Component被容器扫描
3)定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次
13.2 使用
13.2.1 配置和类
启动类中添加:
@EnableScheduling
新建 task 包,新建 TestTask.java
@Component
public class TestTask {
@Scheduled(fixedRate = 2000)
public void sum() {
System.out.println("当前时间: " + new Date());
}
}
13.2.2 常用的定时任务表达式
1、cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒
1)crontab 工具 https://tool.lu/crontab/
2、fixedRate: 定时多久执行一次(上一次开始执行时间点后xx秒再次执行;)
3、fixedDelay: 上一次执行结束时间点后xx秒再次执行
4、fixedDelayString: 字符串形式,可以通过配置文件指定
crontab 参考链接:https://www.cnblogs.com/javahr/p/8318728.html
十四、异步任务
当用户上传一个表格时,后端服务器需要解析数据,并存到数据库。
若是同步,则用户调用上传接口后,需要等待: 上传文件 => 解析数据 => 存到数据库 => 服务器返回上传成功,这时页面会一直卡住
若是异步,则用户调用上传接口后,只要等待: 上传文件 => 服务器返回上传成功 => 服务器解析数据 => 存到数据库,这时页面在上传文件成功后,即可操作。速度无疑快了很多
14.1 异步任务的几种方式
- 创建线程,使用线程处理异步任务 多线程(二) 多线程的三种创建方式及区别
- 使用 springboot 自身的异步方式,在想要异步执行的方法上加上@Async注解,在controller上加上@EnableAsync
14.2 springboot 异步执行的使用
1)在启动类上增加 @EnableAsync , 表示扫描异步组件
@EnableAsync
2)异步任务需要封装到类中,不能直接写在 controller 中。若要监控是否完成, 增加Future<String> 返回结果 AsyncResult<String>("task执行完成")。
@Component
public class AsyncTask {
@Async
public void task1() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(1000L);
long end = System.currentTimeMillis();
System.out.println("校验 耗时 = " + (end - begin));
}
@Async
public void task2() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(2000L);
long end = System.currentTimeMillis();
System.out.println("发邮件 耗时 = " + (end - begin));
}
@Async
public void task3() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(3000L);
long end = System.currentTimeMillis();
System.out.println("保存用户信息 耗时 = " + (end - begin));
}
// 获取异步结果
@Async
public Future<String> task4() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(3000L);
long end = System.currentTimeMillis();
System.out.println("任务4 耗时 = " + (end - begin));
return new AsyncResult<String>("任务4");
}
// 获取异步结果
@Async
public Future<String> task5() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(3000L);
long end = System.currentTimeMillis();
System.out.println("任务5 耗时 = " + (end - begin));
return new AsyncResult<String>("任务5");
}
// 获取异步结果
@Async
public Future<String> task6() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(3000L);
long end = System.currentTimeMillis();
System.out.println("任务6 耗时 = " + (end - begin));
return new AsyncResult<String>("任务6");
}
}
3)controller 中使用
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncTask task;
@GetMapping("task")
public JsonData exeTask() throws InterruptedException {
long begin = System.currentTimeMillis();
// task.task1();
// task.task2();
// task.task3();
Future<String> task4 = task.task4();
Future<String> task5 = task.task5();
Future<String> task6 = task.task6();
for (;;) {
if (task4.isDone() && task5.isDone() && task6.isDone()) {
break;
}
}
long end = System.currentTimeMillis();
System.out.println("执行总耗时 = " + (end-begin));
return JsonData.buildSuccess((end-begin));
}
}
十五、使用日志框架
15.1 常用的日志框架
- slf4j
- log4j
- logback
- common-logging
15.2 logback 使用
15.2.1 logback 介绍
基于 Log4j 的基础上大量改良,不能单独使用,推荐配合SLF4J使用
15.2.2 logback 核心对象
logger: 日志记录器
Appender: 指定日志输出的目的地,目的地可以是控制台,也可以是文件
Layout: 日志布局,格式化日志信息的输出
15.2.3 日志级别
DEBUG < INFO < WARN < ERROR
15.2.4 logback 使用
1) 使用 -spring.xml 结尾,便于spring扫描到
默认加载加载配置顺序 logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
2) 填写 logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- name: 自定义的名称 class: logback提供的类 -->
<appender name="consoleApp" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</layout>
</appender>
<appender name="fileInfoApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>app_log/log/app.info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<appender name="fileErrorApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 设置滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>app_log/log/app.err.%d.log</fileNamePattern>
<!-- 控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,
且<maxHistory> 是1,则只保存最近1个月的文件,删除之前的旧文件 -->
<MaxHistory>1</MaxHistory>
</rollingPolicy>
</appender>
<!-- 控制总的输出级别 -->
<root level="WARN">
<!-- 指定输出目的 -->
<appender-ref ref="consoleApp"/>
<appender-ref ref="fileInfoApp"/>
<appender-ref ref="fileErrorApp"/>
</root>
</configuration>
十六、搜索框架 ElastcSeacrh 的使用
16.1 ElastcSearch 简介
16.1.1 ElastcSearch 做什么
ElastcSearch 用于对处理面对海量数据的搜索,如京东商城,淘宝搜索等
16.1.2 特点
- 全文检索,结构化检索,数据统计、分析,接近实时处理,分布式搜索(可部署数百台服务器),处理PB级别的数据搜索纠错,自动完成
- 使用场景:日志搜索,数据聚合,数据监控,报表统计分析
- 国内外使用者:维基百科,Stack Overflow,GitHub
16.2 ElastcSearch 使用
1)需要使用 jdk 1.8,下载压缩包并解压
# 下载压缩包
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.8.tar.gz
# 解压
tar -zxvf elasticsearch-5.6.8.tar.gz
# 确定 java 版本 (应为 1.8)
java -version
2)使用
./bin/elasticsearch
若碰到错误或警告,请看:elasticsearch 报错问题汇总
3)RESTAPI
(请求方法 RESTAPI)
# 查看集群状态
GET /_cat/health?v
# 查看所有node
GET /_cat/nodes?v
# 查看所有索引
GET /_cat/indices?v
# 创建索引
PUT /customer?pretty
# 加入数据
# customers对应数据库, external对应表,展示为 _type 1为id
PUT /customer/external/1?pretty
在 body 中加入: {"name": "John Doe"}
# 获取数据
GET /customer/external/1?pretty
16.3 springboot 整合 elasticsearch
16.3.1 加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
16.3.2 增加配置
在 application.properties 文件中增加:
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.47.135:9300
spring.data.elasticsearch.repositories.enabled=true
16.3.3 相关代码
ArticleRepository.java
@Component
public interface ArticleRepository extends ElasticsearchRepository<ArticleEntity, Long> {
}
ArticleEntity.java
// 存到 blog 数据库, artical 表
@Document(indexName= "blog", type="artical")
public class ArticleEntity {
private static final long serialVersionUID = 1L;
private long id;
private String title;
private String summary;
private String content;
private int pv;
public static long getSerialVersionUID() {
return serialVersionUID;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getPv() {
return pv;
}
public void setPv(int pv) {
this.pv = pv;
}
}
ArticleController.java
@RestController
@RequestMapping("/article")
public class ArticleController {
@Autowired
private ArticleRepository articleRepository;
@GetMapping("save")
public Object save() {
ArticleEntity articleEntity = new ArticleEntity();
articleEntity.setId(5L);
articleEntity.setPv(888);
articleEntity.setContent("this is content");
articleEntity.setTitle("springboot");
articleEntity.setSummary("概要");
articleRepository.save(articleEntity);
return JsonData.buildSuccess();
}
@GetMapping("search")
public Object search(String title) {
QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", title);
Iterable<ArticleEntity> list = articleRepository.search(queryBuilder);
return JsonData.buildSuccess(list);
}
}
16.3.4 使用
存储:
http://localhost:8095/article/save
检查:
访问服务器上的 elasticsearch 端口,查看对应的索引和类型
http://192.168.47.135:9200/blog/artical/2
调用接口查询:
使用了 QueryBuilder 查询相关数据
http://localhost:8095/article/search?title=springboot
十七、ActiveMQ 和 RocketMQ 的使用
17.1 消息队列简洁
17.1.1 消息队列的作用
- 跨平台
- 多语言
- 多项目
- 解耦
- 分布式事务
- 流量控制
- 最终一致性
- RPC调用
17.1.2 消息队列内容
- JMS提供者:Apache ActiveMQ、RabbitMQ、Kafka、Notify、MetaQ、RocketMQ
- JMS生产者(Message Producer)
- JMS消费者(Message Consumer)
- JMS消息
- JMS队列
- JMS主题
17.2 ActiveMQ 的使用
17.2.1 ActiveMQ 安装
1)下载安装包
官网下载:http://activemq.apache.org/activemq-5153-release.html
使用手册:http://activemq.apache.org/getting-started.html
百度网盘下载:
链接:https://pan.baidu.com/s/1qH4mozanAteOyCDIvNVWcw
提取码:t5s7
2)解压安装包
tar -zxvf apache-activemq-5.15.3-bin.tar
3)启动activemq服务
cd /usr/local/apache-activemq-5.15.3/bin
./activemq start
4)开放端口
网页访问端口:8161
JAVA 服务访问端口: 61616
# 打开对应端口
firewall-cmd --zone=public --add-port=61616/tcp --permanent
firewall-cmd --zone=public --add-port=8161/tcp --permanent
# 重启防火墙
firewall-cmd --reload
5)访问控制页面,端口为 8161,账号密码默认为 admin admin
# http://xxxxxx:8161
http://192.168.47.135:8161
17.2.2 增加依赖
不同的 springboot 版本,增加的依赖不同: 配置连接池,启动项目报错
<!-- 整合消息队列ActiveMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!-- activemq配置线程池则加入 -->
<!-- springboot2.0+及以下版本 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
<!-- springboot2.1+ -->
<dependency>
<groupId>org.messaginghub</groupId>
<artifactId>pooled-jms</artifactId>
</dependency>
17.2.3 增加 application.properties 配置
######### activemq 配置 #########
#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://192.168.47.135:61616
#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)
spring.activemq.user=admin
spring.activemq.password=admin
#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100
#开启订阅模式
#default point to point
spring.jms.pub-sub-domain=true
17.2.4 使用
1)启动类中添加 @EnableJms :
@EnableJms // 开启JMS
public class DemoApplication {
.....
@Bean // 交给spring管理,方便后续进行注入
public Queue queue() {
return new ActiveMQQueue("article.queue");
}
@Bean
public Topic topic() {
return new ActiveMQTopic("news.topic");
}
// 若要同时支持点对点模式和发布订阅模式,需要给topic定义独立的JmsListenerContainer
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
}
2)编写生产者:
// ProducerService.java
public interface ProducerService {
/**
* 功能描述:指定消息队列,还有消息
* @param destination
* @param message
*/
public void sendMessage(Destination destination, final String message);
/**
* 功能描述:使用默认消息队列, 发送消息
* @param message
*/
public void sendMessage( final String message);
/**
* 功能描述:消息发布者
* @param msg
*/
public void publish(String msg);
}
// ProducerServiceImpl.java
/**
* 功能描述:消息生产者
*/
@Service
public class ProducerServiceImpl implements ProducerService {
@Autowired
private Queue queue;
@Autowired
private JmsMessagingTemplate jmsTemplate; //用来发送消息到broker的对象
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(Destination destination, String message) {
jmsTemplate.convertAndSend(destination, message);
}
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(final String message) {
jmsTemplate.convertAndSend(this.queue, message);
}
//=======发布订阅相关代码=========
@Autowired
private Topic topic;
@Override
public void publish(String msg) {
this.jmsTemplate.convertAndSend(this.topic, msg);
}
}
// OrderController.java
@RestController
@RequestMapping("/mq")
public class OrderController {
@Autowired
private ProducerService producerService;
@GetMapping("order")
public Object order(String msg){
Destination destination = new ActiveMQQueue("order.queue");
producerService.sendMessage(destination, msg);
return JsonData.buildSuccess();
}
@GetMapping("common")
public Object common(String msg){
producerService.sendMessage(msg);
return JsonData.buildSuccess();
}
@GetMapping("publish")
public Object topic(String msg) {
producerService.publish(msg);
return JsonData.buildSuccess();
}
}
3)编写消费者
// OrderConsumer.java
@Component
// 监听是否有消息到来
public class OrderConsumer {
@JmsListener(destination = "order.queue")
public void receiveQueue(String text) {
System.out.println("OrderConsumer 收到的报文为: " + text);
}
}
4)编写订阅者
// TopicSub.java
@Component
public class TopicSub {
@JmsListener(destination = "news.topic")
public void receive(String text) {
System.out.println("news.topic 消费者: reveive = " + text);
}
@JmsListener(destination = "news.topic")
public void receive2(String text) {
System.out.println("news.topic 消费者: reveive2 = " + text);
}
@JmsListener(destination = "news.topic")
public void receive3(String text) {
System.out.println("news.topic 消费者: reveive3 = " + text);
}
}
5)当同时使用 点对点 模式和 发布/订阅 模式时,仅有 发布/订阅 模式有效,需要使用 JMS 工厂:
修改配置文件:
# application.properties
######### activemq 配置 #########
#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://192.168.47.135:61616
#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)
spring.activemq.user=admin
spring.activemq.password=admin
#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100
#开启订阅模式, 若要同时兼容p2p和pub/sub模式,则要注释掉
#default point to point
#spring.jms.pub-sub-domain=true
添加 JmsListenerContainerFactory:
@EnableJms // 开启JMS
public class DemoSpringApplication {
public static void main(String[] args) {
SpringApplication.run(HelloSpringApplication.class, args);
}
@Bean // 交给spring管理,方便后续进行注入
public Queue queue() {
return new ActiveMQQueue("article.queue");
}
@Bean
public Topic topic() {
return new ActiveMQTopic("news.topic");
}
// 若要同时支持点对点模式和发布订阅模式,需要给topic定义独立的JmsListenerContainer
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
}
修改订阅代码, 增加: containerFactory = "jmsListenerContainerTopic"
// TopicSub.java
@Component
public class TopicSub {
@JmsListener(destination = "news.topic", containerFactory = "jmsListenerContainerTopic")
public void receive(String text) {
System.out.println("news.topic 消费者: reveive = " + text);
}
@JmsListener(destination = "news.topic", containerFactory = "jmsListenerContainerTopic")
public void receive2(String text) {
System.out.println("news.topic 消费者: reveive2 = " + text);
}
@JmsListener(destination = "news.topic")
public void receive3(String text) {
System.out.println("news.topic 消费者: reveive3 = " + text);
}
}
17.3 RocketMQ 的使用
17.3.1 RocketMQ 简洁
Apache RocketMQ作为阿里开源的一款高性能、高吞吐量的分布式消息中间件。
17.3.2 RocketMQ 特点
- 在高压下1毫秒内响应延迟超过99.6%。
- 适合金融类业务,高可用性跟踪和审计功能。
- 支持发布订阅模型,和点对点
- 支持拉pull和推push两种消息模式
- 单一队列百万消息
- 支持单master节点,多master节点,多master多slave节点
17.3.3 RocketMQ 概念
- Producer:消息生产者
- Producer Group:消息生产者组,发送同类消息的一个消息生产组
- Consumer:消费者
- Consumer Group:消费同个消息的多个实例
- Tag:标签,子主题(二级分类),用于区分同一个主题下的不同业务的消息
- Topic:主题
- Message:消息
- Broker:MQ程序,接收生产的消息,提供给消费者消费的程序
- Name Server:给生产和消费者提供路由信息,提供轻量级的服务发现和路由
17.3.4 RocketMQ 环境搭建
1)安装
官网下载:https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.2.0/rocketmq-all-4.2.0-bin-release.zip
使用手册:http://rocketmq.apache.org/docs/quick-start/
百度网盘:
链接:https://pan.baidu.com/s/1fB79eZolYc_Ynh4u1OrdyQ
提取码:0khs
2)解压:
# 创建 rocketmq 文件夹
mkdir rocketmq
# 解压压缩包到 rocketmq 文件夹下
unzip rocketmq-all-4.2.0-bin-release.zip ./rocketmq
3)启动
启动 namesrv 服务
cd /rocketmq/bin
# 启动 namesrv
nohup sh mqnamesrv &
查看日志文件 nohup.out
tail -f nohup.out
启动成功
启动 broker 服务
nohup sh mqbroker -n 127.0.0.1:9876 &
查看日志文件 nohup.out
tail -f nohup.out
查看服务是否正确开启
jps
4)可能遇到的问题(内存分配)
开启时,你可能在 nohup.out 文件中看到这样的错误提示: Cannot allocate memory (无法分配足够的内存)
通过修改配置文件方式:
参数说明:
- Xms: 为jvm启动时分配的内存,比如-Xms200m,表示分配200M
- Xmx: 为jvm运行过程中分配的最大内存,比如-Xmx500m,表示jvm进程最多只能够占用500M内存
- Xss: 为jvm启动的每个线程分配的内存大小
- MetaSpace: 表示metaspace首次使用不够而触发FGC的阈值
- MaxMetaSpace: 用于设置metaspace区域的最大值
修改 namesrv 服务内存:
vim runserver.sh
修改 broker 服务内存
vim runbroker.sh
4)如何关闭服务
sh bin/mqshutdown namesrv
sh bin/mqshutdown broker
5)控制台搭建
5.1 下载
官网下载:
5.2 编译成 jar 文件
mvn clean install '-Dmaven.test.skip=true'
十八、多环境配置
18.1 为什么要多环境配置
多环境配置可根据当前环境是开发环境(dev)、测试环境(test)、预发布环境(pre)和生产环境(prod),使用不同的配置文件,如链接不同的数据库等。
18.2 如何使用
spring.profiles.active=dev
在resources/config文件夹下创建多个配置文件,并指定使用哪个配置文件:
十八、响应式编程
18.1 为什么要多环境配置
多环境配置可根据当前环境是开发环境(dev)、测试环境(test)、预发布环境(pre)和生产环境(prod),使用不同的配置文件,如链接不同的数据库等。