Springboot + Spring Security + redis + rabbitmq 配置

This article describes the configuration process based on the springboot project to integrate spring Security, MySQL, Redis, RabbitMQ.

Introduce project dependencies

First, create a new gradle project using the IntelliJ editor, then change the build.gradle file to the following content and re-import the dependencies

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
buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
repositories {
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0'
}
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.junit.platform.gradle.plugin'

group 'com.ray.parctice'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.15'
compile group: 'org.flywaydb', name: 'flyway-core', version: '5.2.4'
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-data-redis")
compile group: 'redis.clients', name: 'jedis', version: '3.0.1'
compile("org.springframework.boot:spring-boot-starter-amqp")

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.0.0'

testCompile('org.junit.jupiter:junit-jupiter-params:5.0.0')
testCompile("org.springframework.security:spring-security-test")

testRuntime('org.junit.jupiter:junit-jupiter-engine:5.0.0')

testRuntime "com.h2database:h2"
}

Springboot

Here is to build a basic Springboot project, which is roughly divided into the following steps:

Add mysql configuration item in application.yml

1
2
3
4
5
6
7
8
9
10
spring:
datasource:
url: jdbc:mysql://localhost:3306/myblob?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username: root
password: 123456

driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: validate

New User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//MyUser.java
@Entity
@Table(name = "my_user")
public class MyUser implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;

@Column(nullable = false, unique = true)
private String name;

@Column(nullable = false)
private String password;

private String feeling;

// getters and setters
}
1
2
3
4
//MyUserRepository
@Repository
public interface MyUserRepository extends JpaRepository<MyUser, Integer> {
}

Create

1
2
3
4
5
6
7
//MyUserService.java
public interface MyUserService {
MyUser getUserById(int id) throws Exception;
void registerUser(MyUser user) throws Exception;
MyUser updateUser(MyUser user) throws Exception;
void deleteUserById(int id) throws Exception;
}
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
//MyUserSeiviceImpl.java
package com.jimmy.myBlob.service.impl;

import com.jimmy.myBlob.model.MyUser;
import com.jimmy.myBlob.repository.MyUserRepository;
import com.jimmy.myBlob.service.MyUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class MyUserServiceImpl implements MyUserService {
@Autowired
private MyUserRepository myUserRepository;

public void registerUser(MyUser user) throws Exception{
myUserRepository.save(user);
}

public MyUser getUserById(int id) throws Exception {
return myUserRepository.findById(id).get();
}

@Override
public MyUser updateUser(MyUser user) throws Exception {
return myUserRepository.save(user);
}

@Override
public void deleteUserById(int id) throws Exception {
myUserRepository.deleteById(id);
}
}

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
//MyUserController.java
@RestController
public class MyUserController {
@Autowired
private MyUserService myUserService;

@PostMapping("/users")
public ResponseEntity<?> registerUser(@RequestBody MyUser user) throws Exception {
myUserService.registerUser(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}

@GetMapping("/users/{userId}")
public ResponseEntity<?> getUserById(@PathVariable int userId) throws Exception {
return new ResponseEntity<>(myUserService.getUserById(userId), HttpStatus.OK);
}

@PutMapping("/users")
public ResponseEntity<?> updateUser(@RequestBody MyUser user) throws Exception {
return new ResponseEntity<>(myUserService.updateUser(user), HttpStatus.OK);
}

@DeleteMapping("/users/{userId}")
public ResponseEntity<?> deleteUserById(@PathVariable int userId) throws Exception {
myUserService.deleteUserById(userId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}

So far, I have completed the basic configuration of a most basic Springboot + mysql project, which uses Restful style requests to handle User CRUD.

Configure

In some of our projects, there will be a lot of data. When the amount of data becomes large, the efficiency of our database will become a bottleneck of our project. This is unavoidable, so we can only change one way. To avoid the slow query of a large amount of data in the database.

Although there may be a large amount of data in the project, some data are used frequently or have high speed requirements, such as the data of a hot video, which will be requested by a large number of requests and require fast enough speed, while some data On the contrary, the former is called hot data, and the latter is called cold data. For hot data, we can put them in the cache to improve efficiency.

So we need a way to help us manage cached data, redis as a nosql database stored in memory can help us solve these problems well. Redis is single-threaded, but because it is an in-memory database, the read and write speed is very fast, and because it is single-threaded, it avoids the consumption caused by mechanisms such as process switching and locking caused by multi-threading; redis also has a snapshot mechanism to help us protect our data in the event of a sudden power outage.

Although redis is very powerful, it is very simple to integrate into our spring project.

Install redis

Like other databases, first you need to install redis on your system and run it

Modify the application.yml configuration file

Change the application.yml file to the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
datasource:
url: jdbc:mysql://localhost:3306/myblob?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username: root
password: 123456

driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: validate
cache:
type: redis
redis:
jedis:
pool:
max-idle: 10
max-wait: 1000000
max-active: 100
host: 127.0.0.1
port: 6379
timeout: 10000

Modify the service file and add cache annotation on each request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@CachePut(cacheNames="myUser", key="#user.id")
public void registerUser(MyUser user) throws Exception{
myUserRepository.save(user);
}

@Cacheable(cacheNames="myUser", key="#id")
public MyUser getUserById(int id) throws Exception {
simulateSlowService();
return myUserRepository.findById(id).get();
}

@CachePut(cacheNames="myUser", key="#user.id")
@Override
public MyUser updateUser(MyUser user) throws Exception {
return myUserRepository.save(user);
}

@CacheEvict(cacheNames="myUser", key="#id")
@Override
public void deleteUserById(int id) throws Exception {
myUserRepository.deleteById(id);
}

There are two things to note here

  1. The value of the key needs to correspond to the incoming parameters. For example, the key of the first request is the id attribute in the user, which needs to be written as #user.id, which cannot be written as #myUser.id (cacheNames.id) or #my_user.id (tableName.id). Once you fill in the wrong, you will find that your request reported 500 but the database was written successfully.
  2. The annotations used by CRUD are different. In an environment that supports Spring Cache, for methods that use the @Cacheable annotation, Spring will check whether there is a cache element with the same key in the Cache before each execution. If there is, the method will not be executed. Instead, get the result directly from the cache and return it, otherwise it will be executed and the return result will be stored in the specified cache. @CachePut can also declare a method that supports caching. Unlike @Cacheable, the method using @CachePut annotation does not check whether there are previously executed results in the cache before execution, but executes the method every time and stores the execution results in the form of Attribute - Value Pair in the specified cache. @CacheEvict is used to annotate methods or classes that need to clear cache elements. When the tag is on a class, it means that the execution of all methods in it will trigger the cache clearing operation.

Add @EnableCaching to the project entry

1
2
3
4
5
6
7
@SpringBootApplication
@EnableCaching
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

Spring

Add WebSecurityConfig.java file

1
2
3
4
5
6
7
8
9
10
11
12
13
@EnableWebSecurity
public class WebSecurityConfig {
@Configuration
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/users", "/users/**").permitAll().anyRequest().authenticated()
.and().csrf().disable();
}
}
}

RabbitMQ

Install and start

You can search online for this step, there are many tutorials.

Modify application.yml configuration

Add the configuration of rabbitmq under the spring configuration

1
2
3
4
5
6
7
8
rabbitmq:
host: localhost
port: 15672
username: guest
password: guest
publisher-confirms: true
publisher-returns: true
template.mandatory: true

Add the RabbitConstants class to get configuration information in the configuration file

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
//RabbitConstants.java
@Component
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitConstants {
private String username;

private String password;

private int port;

private String host;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public void setPort(int port) {
this.port = port;
}

public void setHost(String uhost) {
this.host = uhost;
}


public int getPort() {
return port;
}

public String getHost() {
return host;
}


}

There are two ways to get the configuration here. The first is to add the @ConfigurationProperties (prefix = “spring.rabbitmq”) annotation directly above the class, so the properties in the class with the same name as those in the configuration file will be automatically matched; The second way is to add the @Value annotation directly to the property, but this way needs to be fully specified.

Add RabbitmqConfig file

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
@Configuration
public class RabbitmqConfig {

public static final String EXCHANGE = "spring-boot-exchange2";
public static final String ROUTINGKEY = "spring-boot-routingKey2";

@Autowired
private RabbitConstants rabbitConstants;
@Bean
public CachingConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(rabbitConstants.getHost());
connectionFactory.setUsername(rabbitConstants.getUsername());
connectionFactory.setPassword(rabbitConstants.getPassword());
connectionFactory.setVirtualHost("/");
//The call needs to be displayed here before the message can be called back, which must be set.
connectionFactory.setPublisherConfirms(true);
return connectionFactory;
}

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
return template;
}


@Bean
public DirectExchange defaultExchange() {
return new DirectExchange(EXCHANGE);
}

@Bean
public org.springframework.amqp.core.Queue queue() {
return new Queue("spring-boot-queue", true);
}

@Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(defaultExchange()).with(RabbitmqConfig.ROUTINGKEY);
}


@Bean
public SimpleMessageListenerContainer messageContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
container.setQueues(queue());
container.setExposeListenerChannel(true);
container.setMaxConcurrentConsumers(1);
container.setConcurrentConsumers(1);
//Set confirmation mode manual confirmation
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
container.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
byte[] body = message.getBody();
System.out.println("receive msg : " + new String(body));
Confirm that the message was successfully consumed
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
});
return container;
}

}

Add RabbitMQ message sender

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
@Component
public class RabbitmqSendMessage implements RabbitTemplate.ConfirmCallback {
private RabbitTemplate rabbitTemplate;


/**
* Constructor method injection
*/
@Autowired
public RabbitmqSendMessage(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
//This is to set the callback to be received and sent to the response, confirm () is explained below
rabbitTemplate.setConfirmCallback(this);
}

public void sendMsg(String content) {
CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
//convertAndSend (exchange: switch name, routingKey: routing key, object: message content sent, correlationData: message ID)
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE, RabbitmqConfig.ROUTINGKEY, content, correlationId);
}

@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println ("callback id:" + correlationData);
if (ack) {
System.out.println ("Message successfully consumed");
} else {
System.out.println ("message consumption failed:" + cause);
}
}
}

Modify Controller file, add rabbitmq

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
//MyUserController.java
@RestController
public class MyUserController {
@Autowired
private MyUserService myUserService;

@Resource
private RabbitTemplate rabbitTemplate;

@Resource
private RabbitmqSendMessage rabbitmqSendMessage;

@PostMapping("/users")
public ResponseEntity<?> registerUser(@RequestBody MyUser user) throws Exception {
myUserService.registerUser(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}

@GetMapping("/users/{userId}")
public ResponseEntity<?> getUserById(@PathVariable int userId) throws Exception {
rabbitmqSendMessage.sendMsg("123");
return new ResponseEntity<>(myUserService.getUserById(userId), HttpStatus.OK);
}

@PutMapping("/users")
public ResponseEntity<?> updateUser(@RequestBody MyUser user) throws Exception {
return new ResponseEntity<>(myUserService.updateUser(user), HttpStatus.OK);
}

@DeleteMapping("/users/{userId}")
public ResponseEntity<?> deleteUserById(@PathVariable int userId) throws Exception {
myUserService.deleteUserById(userId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}