0%

文章字数:835,阅读全文大约需要3分钟

一、配置相关

@springBootApplication
通常在主类上,包含:

  • @Configuration:表明是配置文件
  • @EnableAutoConfiguration:根据依赖自动配置springBoot
  • @ComponentScan:告诉Spring从哪里找bean(注解的包及下级包)

@Profiles
隔离应用程序配置,让配置在特定环境生效。
标注于@Component 或者 @Configuration

二、注入Bean

@Repository
标记数据库访问组件DAO。

@Service
标记业务层组件。

@RestController
标记控制层组件,包含:

  • @Controller:标记控制层组件
  • @ResponseBody:根据前端dataType将返回的对象转换成相应格式jsonxml

@Component
泛指组件,不好归类时标记

@Bean
在方法上使用,产生一个bean,交给spring

三、使用Bean

@AutoWired
自动注入,可以作用于成员变量、方法、构造方法。(required=false)找不到bean也不报错

@Qualifier
当AutoWired有多个同类型bean时(“name”)指定

@Resource(name=”name”,type=”type”):
没有括号内内容的话,默认byName。与@Autowired干类似的事。

四、网络请求相关

@RequestMapping
处理请求的地址映射,可在类与方法上。类上代表是方法上的父路径。
六个属性:

  • params:指定request中必须有某参数
  • headers:request中必须有指定的header
  • value:指定请求实际地址
  • method:指定类型,GET、POST….
  • consumes:指定处理提交类型(Content-Type),application/json,test/html等

@RequestParam
方法的参数前,获取指定名称的参数并传入形参(键值对类型的参数)

1
public User getUser(@RequestParam(value = "id", required = false) Integer id)

required=false表示该值可不存在,可为空。也可以作用于@Autowired表示该bean不是必须的,可不存在,可为空。

@PathVariable
路径变量,获取RequestMapping中占位符的值

1
2
3
4
@RequestMapping("/user/{uid}")
public String getUserById(@PathVariable Integer id){
//do something
}

@RequestBody
可以接收json格式的数据(应该可省略,直接写实体类目前可以直接转换)

1
public DbBook findBookByName(@RequestBody DbBook book)

接收字符串(不能组成一个实体类,所以不能用实体类接收的情况)

1
public void deleteBookById(@RequestBody Map<String, String> map)

@RequestBody接收不同的对象

  1. 创建一个新的entity,将两个entity都进去。
  2. 用Map<String, Object>接受request body,自己反序列化到各个entity中。
  3. 实现自己的HandlerMethodArgumentResolver

@ModelAttribute
和实体类绑定

1
public String modelAttributeTest(@ModelAttribute User user)

注:multipart/form-data(二进制文件)@RequestBody不能处理这种格式的数据。
application/json、application/xml等格式的数据,必须使用@RequestBody来处理。

五、全局异常处理

@ControllerAdvice
control增强器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.ui.Model;
import java.util.HashMap;
import java.util.Map;

/**
* controller 增强器,作用于所有有效的control
*/
@ControllerAdvice
public class MyControllerAdvice {

/**
* 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {}

/**
* 把值绑定到Model中,使全局@RequestMapping可以获取到该值
* @param model
*/
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("author", "Magical Sam");
}

/**
* 全局异常捕捉处理
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public Map errorHandler(Exception ex) {
Map map = new HashMap();
map.put("code", 100);
map.put("msg", ex.getMessage());
return map;
}

}

@ExceptionHandler(Exception.class):
标注在方法上,遇到这个异常就执行以下方法

六、JPA

  • 建表时的附加语句,可以用来指定类型,默认值等
  1. tinyint类型的默认值@Column(name="state",columnDefinition="tinyint default 0")
  2. varchar(128),非空@Column(name = "Email",columnDefinition="varchar(128) not null")

当String类型不够用时可以指定属性在数据库中的类型为text

1
@Column(name = "Remark",columnDefinition="text")
  • uuid,见jpa主键生成策略

  • query执行sql语句

    1
    2
    3
    4
    5
     @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);

    @Query("select * from User u where u.name like :first and u.age>:age")
    List<User> findByNameLikeAndAgeGreaterThan(@Param("first")String firstName,@Param("age")Integer age);

七、值注入

  • @Value("${key}")

    通过key注入配置文件中的value

  • @Value("${myValue:#{null}}")注入非必填的
    @Value 想要非必填 required false

  • set上加

    1
    2
    3
    4
    @Value("${key}")
    public void setKeyvalue(String keyvalue){
    this.keyvalue=keyvalue;
    }
  • 构造方法注入

    1
    2
    3
    4
    5
    6
    class Message{
    private String text;
    Message(@Value("${app.text}") String text){
    this.text = text;
    }
    }

文章字数:153,阅读全文大约需要1分钟

一、引入

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

二、配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8081
spring:
#数据库连接配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.107.105.158:3306/test?characterEncoding=utf-8&useSSL=false
username: root
password: 123456

#mybatis的相关配置
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.demo.mybatis.model
#开启驼峰命名
configuration:
map-underscore-to-camel-case: true

三、实体类

  • 位置在type-aliases-package配置的包中
    1
    2
    3
    4
    5
    6
    public class User implements Serializable {
    private Long id;
    private String username;
    private String password;
    //...get set方法
    }

四、DAO层

1
2
3
4
@Mapper
public interface UserMapper {
List<User> findAll();
}
  • mapper-locations中配置的位置编写和接口对应的映射表

UserMapper.xml

1
2
3
4
5
6
7
<?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.demo.mybatis.mapper.UserMapper">
<select id="findAll" resultType="User">
SELECT * FROM USER
</select>
</mapper>

五、使用

1
2
3
// 注入即可直接使用
@Autowired
private UserMapper userMapper;

文章字数:334,阅读全文大约需要1分钟

swagger2是一款帮助我们生成restful接口细节记录信息的工具。

引入Swagger2

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>

创建配置类

Application.java的同级创建Swagger2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration //标注为配置类
@EnableSwagger2 //启动Swagger2
public class Swagger2 {

@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.didispace.web"))
.paths(PathSelectors.any())
.build();
}

//基本信息
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("标题")
.description("介绍")
.termsOfServiceUrl("url")
.contact("name")
.version("1.0")
.build();
}

}

丰富文档内容

  • @ApiOperation给api增加说明
  • @ApiImplicitParams包裹多个参数注解信息
  • @ApiImplicitParam参数注解信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@RestController
@RequestMapping(value="/users") // 通过这里配置使下面的映射都在/users下,可去除
public class UserController {

static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

@ApiOperation(value="获取用户列表", notes="")
@RequestMapping(value={""}, method=RequestMethod.GET)
public List<User> getUserList() {
List<User> r = new ArrayList<User>(users.values());
return r;
}

@ApiOperation(value="创建用户", notes="根据User对象创建用户")
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
@RequestMapping(value="", method=RequestMethod.POST)
public String postUser(@RequestBody User user) {
users.put(user.getId(), user);
return "success";
}

@ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public User getUser(@PathVariable Long id) {
return users.get(id);
}

@ApiOperation(value="更新用户详细信息", notes="根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
})
@RequestMapping(value="/{id}", method=RequestMethod.PUT)
public String putUser(@PathVariable Long id, @RequestBody User user) {
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}

@ApiOperation(value="删除用户", notes="根据url的id来指定删除对象")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id) {
users.remove(id);
return "success";
}

}

启动springBoot,访问http://localhost:8080/swagger-ui.html就能看到详细信息。

调试API

进入Swagger2的web界面,点击进入api,里面有调试api的按钮,并且黄色区域有参数的模板。

生产环境禁用

1
2
swagger2:
enable: false

文章字数:39,阅读全文大约需要1分钟

简便方法

  1. 使用file.transferTo("地址/文件名")
    1
    2
    3
    4
    5
    6
    7
    8
    public void upload(@RequestParam("file") MultipartFile file) {
    try {
    file.transferTo(new File("/file/upload/" + file.getName()));
    } catch (Exception e) {
    e.printStackTrace();
    }
    return;
    }
  2. IOUTils
    import org.apache.commons.io.IOUtils;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void upload(@RequestParam("file") MultipartFile file){
    file file1 = new File("c:\\xxx\\aaa","a.txt");
    //原始文件输入流
    InputStream inputStream = file.getInputStream();
    //新文件输出流
    FileOutputStream fosm = new FileOutputStream (file1);
    IOUtils.copy(inputStream,fosm );
    inputStream.close();
    fosm .close();
    }

文章字数:16,阅读全文大约需要1分钟
spring加载服务相关信息的类
org.springframework.boot.autoconfigure.web.ServerProperties.class


文章字数:282,阅读全文大约需要1分钟

使用SpringBoot进行MQTT的推送和订阅主题。

环境

  1. jdk1.8

  2. maven

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>

使用

  1. 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;

@Configuration
// spring Integration组件扫描,MessageChannel使用的就是这个组件
@IntegrationComponentScan
public class MqttConfig {

private String username = "userName";
private String password = "password";

/**
* mqtt 服务地址
*/
@Value("${mqtt.url}")
private String hostUrl;

/**
* 设备id,用来区分不同的设备连接
*/
@Value("${mqtt.client.id}")
private String clientId;

/**
* 订阅那个主题
*/
@Value("${mqtt.topic}")
private String defaultTopic;

/**
* 创建连接的工厂
* 用于构建MessageHandler
* @return
*/
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions mqttConnectOptions=new MqttConnectOptions();
// mqtt服务器url
mqttConnectOptions.setServerURIs(new String[]{hostUrl});
// 设置会话心跳时间(秒)
mqttConnectOptions.setKeepAliveInterval(2);
// 每次请求是否清空连接记录
mqttConnectOptions.setCleanSession(false);
// 可以设置用户名密码
mqttConnectOptions.setUserName(username);
mqttConnectOptions.setPassword(password.toCharArray());
factory.setConnectionOptions(mqttConnectOptions);
return factory;
}

/* --------------------发布配置----------------- */

/**
* 1. 发布信息的MessageHandler
* 订阅 mqttOutboundChannel 通道的信息
* @param mqttClientFactory
* @return
*/
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(MqttPahoClientFactory mqttClientFactory) {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientId, mqttClientFactory);
messageHandler.setAsync(true);
messageHandler.setDefaultQos(0);
messageHandler.setDefaultRetained(false);
messageHandler.setAsyncEvents(false);
return messageHandler;
}

@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}

/* --------------------接收配置-------------------- */

/**
* 处理订阅的MessageHandler
* 订阅 aaInboundChannel 通道的信息
* @return
*/
@Bean
@ServiceActivator(inputChannel = "mqttInboundChannel")
public MessageHandler newHandler() {
return message -> System.out.println("收到消息 = " + message.getPayload());
}

@Bean
public MessageChannel mqttInboundChannel() {
return new DirectChannel();
}

/**
* 1. 订阅主题,可订阅多个主题
* 2. 将主题返回的内容发布到指定的 MessageChannel 里
* @param mqttClientFactory
* @return
*/
@Bean
public MessageProducerSupport mqttInbound(MqttPahoClientFactory mqttClientFactory) {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientId, mqttClientFactory, defaultTopic);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(0);
adapter.setOutputChannel(mqttInboundChannel());
return adapter;
}
}

上面配置了订阅了一个DirectChannel,接收到消息后推送给MQTT
下面就是如何使用

1
2
3
4
5
6
7
@Component
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MsgWriter {
void sendToMqtt(String data);
void sendToMqtt(String payload,@Header(MqttHeaders.TOPIC) String topic);
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}

文章字数:325,阅读全文大约需要1分钟

web三大组件

  • Servlet
  • Filter
  • ``

Servlet运行时插件

  • META-INF/services/javax.servlet.ServletContaonerInitializer创建这个文件

  • 继承ServletContainerInitializer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @HandlesTypes(value = {MyService.class}) // 指定需要加载的父类
    public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {
    // arg0: 上面指定的父类的子类会全部传过来
    // arg1: 应用上下文,可以注册Servlet、Listener、Filter
    arg1.addServlet("servletName", new xxxServlet()).addMapping("/myServlet");
    arg1.addListener(OrderListener.class);
    // filter比较麻烦。。。
    }
    }
  • 将类的位置写入META-INF/services/javax.servlet.ServletContaonerInitializer

  • SpringMVC中也实现了一个,需要运行前加载的类继承AbstractAnnotationConfigDospatcherServletInitializer

  • WEB容器加载之前会创建此对象,调用其中方法初始化容器以前的一个控制器
    getRootConfigClasses根容器(Spring的Config文件,注入bean和指定扫描位置的)
    getServletConfigClasses子容器(Spring的Config文件,注入bean和指定扫描位置的)
    ``

SpringMVC

  • 核心类是DispatcherServlet

  • SpringMvcConfigurer可以进行代替xml的配置,例如jsp根目录,静态资源目录,拦截器等

servlet3.0

  • 异步执行业务,链接池线程直接返回。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @WebServlet("/order", asyncSupported = true)// 开启异步
    public class MyServlet extend HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException{
    AsyncContext startAsync = req.startAsync(); // 打开一个异步执行
    startAsync.start(new Runnable(){
    @Override
    public void run() {
    try {
    //...业务逻辑
    startAsync.complete();// 通知容器,执行结束
    AsyncContext asyncContext = req.getAsyncContext();
    ServletResponse response = asyncContext .getReponse();
    response.getWriter().write("res.....success");// 返回结果
    } catch (Exception e){}
    }
    });
    }
    }
  • SpringMVC实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    @RestController
    public class AsyncOrderController {
    @RequestMapping("/order1")
    public Callable<String> order1() {
    Callable<String> callable = new Callable<String>() {
    @Override
    public String call() throws Exception {
    return "success";
    }
    };
    return callable;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    @RequestMapping("/doOrder")
    public DeferredResult<Object> doOrder() {
    DeferredResult<Object> deferredResult = new DeferredResult<> ((long) 5000, "fail..."); // 设置超时时间以及超时返回的值
    new Thread(()->{
    deferredResult.setResult("success"); // 调用这个就可以异步返回执行结果
    }).run();
    }

文章字数:263,阅读全文大约需要1分钟

java程序与第三方系统交互的时候使用同步容易造成响应迟缓,解决方法除了多线程之外还可以使用Spring内置的@Async来解决这个问题

开启注解

1
2
3
4
5
6
7
8
9
//springBoot启动类上开启
@ComponentScan(basePackages = { "com.xwj.controller", "com.xwj.service" })
@EnableAsync //开启异步调用
@EnableAutoConfiguration
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

无返回值调用

1
2
3
4
@Async  //标注使用
public void asyncMethodWithVoidReturnType() {
System.out.println("获取:"+ Thread.currentThread().getName());
}

有返回值

异步方法

1
2
3
4
5
6
7
8
9
//Future接口类型的get方法可以在没有执行之前阻塞,直到获取到值
@Async
public Future<String> asyncMethodWithReturnType() {
try{
Thread.sleep(5000);//模拟执行耗时
return new AsynResult<String>("hello");//返回类型是AsynResult;
}
return null;
}

调用

1
2
3
4
5
6
7
public void test() throws InterruptedException, ExecutionException{
Future<String> future = asyncMethodWithReturnType();//调用
future.get();//获取结果,如果工作没结束阻塞线程。
future.cancel(boolean mayInterruptIfRunning);//停止任务,通过传入值判断是否可以停止。如果可以(在运行)返回true,不可以(运行结束,或无法停止)返回false。
future.isDone();//判断方法是否完成
future.isCancel();//是否可以被取消
}

Future接口

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args)throws InterruptedException,ExecutionException{
Callable ca1 = new Callable(){//线程1
@Override
public String call() throws Exception{
return "xxx";
}
//使用FutureTask包裹
FutureTask<String> ft1 = new FutureTask<String>(ca1);
new Thread(ft1).start();//运行
System.out.println(ft1.get());//获取值
}
}

文章字数:215,阅读全文大约需要1分钟

订阅

  • Spring提供了一个统一订阅监听的工具类,可以将多个监听复用一个连接
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class RedisListenerConfig {
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory, MyListener mylistener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 添加监听
container.addMessageListener(mylistener, new ChannelTopic("topicName"));
return container;
}
}
1
2
3
4
5
6
7
8
9
10
@Component
public class MyListener implements MessageListener {
// 如何处理消息
@Override
public void onMessage(Message message, byte[] pattern) {
final byte[] messageBody = message.getBody();
String messageStr = new String(messageBody, StandardCharsets.UTF_8);
System.out.println("messageStr = " + messageStr);
}
}

使用适配器订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory, MyService myService) {
// 初始化
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 添加适配器,使用反射调用myService的dealMess方法处理
// dealMess方法只有一个入参,参数类型和通道发送时的一致即可
MessageListenerAdapter transactionAdapter =
new MessageListenerAdapter(myService, "dealMess");
// 适配器初始化
transactionAdapter.afterPropertiesSet();
// 监听'myChannel'通道,并用上面的适配器处理
container.addMessageListener(transactionAdapter, new ChannelTopic("myChannel"));
return container;
}

发布

1
2
String mess = "发送消息";
redisTemplate.convertAndSend("topicName", mess.getBytes(StandardCharsets.UTF_8));

文章字数:675,阅读全文大约需要2分钟

通过类上设置注解@EnableScheduling可以开启spring自带的定时任务,@Scheduled设置定时时间。还可以通过ThreadPoolTaskSchedulerschedule(Runable,cron)动态添加

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@EnableScheduling //定时任务
public class SchedulingTest{
//每30秒执行一次
@Scheduled(fixedRate = 1000 * 30)
public void doSomeThing(){
//没30秒输出一次
System.out.println ("定时输出:" + dateFormat ().format (new Date ()));
}

//在固定时间执行(当时间匹配规则时输出)
@Scheduled(cron = "0 */1 * * * * ")
public void reportCurrentByCron(){
System.out.println ("固定时间输出" + dateFormat ().format (new Date()));
}

private SimpleDateFormat dateFormat(){
return new SimpleDateFormat ("HH:mm:ss");
}
}

固定时间匹配规则

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
空, 1970-2099 , - * /
  • 每个元素都可以是一个值如6,一个区间9-12 一个间隔时间8-18/4 /4代表间隔四个小时,一个列表1,3,5
  • 日期和星期互斥,即两个元素重合,必须其中一个设置?忽略
  • *代表所有可能的值
  • /指定数值的增量,如0/10(分钟单位中)代表0分钟开始,10分钟执行一次
  • ?仅在日期和星期中,代表不指定值
  • L用于日期和星期中,代表倒数第几个
  • W仅在日其中,代表平日(工作日)。15W代表离15号最近的一个工作日。
  • C日期,5C五个单位后的第一天
  • #每个月第几个星期几,例如在4#2表示某月的第二个星期三。

转换异步线程

单线程执行时间超过定时间隔可能会出现任务丢失的情况,可以使用异步线程避免这个问题。

  • 配置Spring@EnableAsync
  • 执行方法上配置任务线程池@Async
    1
    2
    3
    4
    5
    6
    //每30秒执行一次
    @Async("taskExecutor")
    @Scheduled(fixedRate = 1000 * 3)
    public void xxx(){
    //...
    }

分布式情况下避免重复执行

  1. lock = redisTemplate.opsForValue().setIfAbsent(KEY, LOCK);采用Redis判断是否存在key,不存在则设置key,执行完成删除key的方式加锁(跨时区部署还是会重复执行)
  2. shedlock加锁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>0.16.1</version>
    </dependency>

    <dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>0.16.1</version>
    </dependency>
    配置(jdbc),还有redis,mongo,zookeeper等锁的实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Configuration
    @EnableScheduling
    public class ShedlockConfig {

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
    return new JdbcTemplateLockProvider(dataSource);
    }

    @Bean
    public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
    return ScheduledLockConfigurationBuilder
    .withLockProvider(lockProvider)
    .withPoolSize(10)
    .withDefaultLockAtMostFor(Duration.ofMinutes(10))//lock expire最大时间10分钟
    .build();
    }
    }
    shedlock表
    1
    2
    3
    4
    5
    6
    7
    CREATE TABLE shedlock(
    name VARCHAR(64),
    lock_until TIMESTAMP(3) NULL,
    locked_at TIMESTAMP(3) NULL,
    locked_by VARCHAR(255),
    PRIMARY KEY (name)
    )
    加锁
    1
    2
    3
    4
    5
    @Scheduled(fixedDelay = 10*1000 /**ms**/,initialDelay = 10*1000)
    @SchedulerLock(name = "demoLockTask", lockAtMostFor = 5*1000)
    public void checkTask(){
    LOGGER.error("checkTask executed");
    }

动态添加关闭定时任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 引入定时调度线程池
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;

// 接收线程调度返回的结果获取类
private ScheduledFuture<?> future;

public void test(){
// 如果已经有任务了就取消原有任务
if (future != null) {
future.cancel(true);
}
// 每月第一天
String cron = "0 0 0 1 * ?";
future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger(cron));
// Runnable也可以写成lambda
future = threadPoolTaskScheduler.schedule(()->{
//do..
}, new CronTrigger(cron));
}