本文讲述的是基于springboot项目来整合spring Security,mysql,redis,rabbitmq的配置过程。
引入项目依赖
首先利用 IntelliJ 编辑器新建一个 gradle 项目,然后将build.gradle文件改为以下内容,重新导入依赖
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 + mysql 配置
这里就搭建一个最基本的Springboot项目,大概分为以下几步:
在application.yml中添加mysql的配置项
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
|
新建User Entity 以及对应的 Respository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @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; }
|
1 2 3 4
| @Repository public interface MyUserRepository extends JpaRepository<MyUser, Integer> { }
|
新建 MyUser 的 Service 和 Controller
1 2 3 4 5 6 7
| 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
| 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
| @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); } }
|
到这里为止就完成了一个最基础的Springboot + mysql 项目的基本配置,这个项目采用的是Restful风格的请求,可以处理User的CRUD。
配置 redis 作为缓存
在我们的某些项目中会存在大量的数据,当数据量变大的时候,我们的数据库的效率就会变成我们项目的一个瓶颈,这一点是无法避免的,所以我们只能改变一种方式来避开大量数据在数据库中查询慢的情况。
虽然项目中可能会存在大量数据,但是有的数据使用频率比较高或者说是对于速度要求比较高,比如某个热点视频的数据,它会被大量请求同时又要求速度够快,而有的数据则正好相反,前者叫做热数据,后者叫做冷数据,对于热数据我们可以将它们放到缓存中来提高效率。
所以我们需要一种方式来帮助我们管理缓存的数据,redis 作为一种存储在内存中的 nosql 型数据库可以很好地帮助我们解决这些问题。redis 是单线程的,但是因为它是内存数据库,所以读写速度非常快,同时由于它是单线程的所以避免了多线程带来的进程切换和加锁等机制带来的消耗;redis 也存在快照机制,帮助我们在突然断电的情况下保护我们的数据。
虽然redis 很强大,但是整合到我们的spring项目中非常简单。
安装redis
和其他数据库一样,首先你需要在自己的系统上安装redis并将其运行
修改application.yml配置文件
将application.yml文件改为以下内容
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
|
修改Service文件,在每个请求上面添加cache注解
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); }
|
这里有两点需要注意
- key的值需要与传入的参数对应,比如第一个请求的key就是user中的id属性,那就需要写成#user.id,这里不能写成#myUser.id(cacheNames.id)或者#my_user.id(tableName.id),一旦填错就会发现你的请求报了500但是数据库写入成功。
- CRUD用到的注解不同,在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。 @CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
在项目入口添加@EnableCaching
1 2 3 4 5 6 7
| @SpringBootApplication @EnableCaching public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
|
Spring Security 配置
添加 WebSecurityConfig.java 文件
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 配置
安装并启动 RabbitMQ
这一步可以去网上搜索一下,有很多教程
修改application.yml配置
在spring 配置下添加rabbitmq的配置
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
|
添加RabbitConstants类去获取配置文件中的配置信息
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
| @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; }
}
|
这里提供了两种方式去获取配置,第一种是直接在类上方加@ConfigurationProperties(prefix = “spring.rabbitmq”)注解,那么类中与配置文件中名字相同的属性会被自动匹配过来;第二种方式是直接在属性上加@Value注解,但是这种方式就需要完全指定。
添加RabbitmqConfig文件
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("/"); 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); 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)); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } }); return container; }
}
|
添加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
| @Component public class RabbitmqSendMessage implements RabbitTemplate.ConfirmCallback { private RabbitTemplate rabbitTemplate;
@Autowired public RabbitmqSendMessage(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; rabbitTemplate.setConfirmCallback(this); }
public void sendMsg(String content) { CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString()); rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE, RabbitmqConfig.ROUTINGKEY, content, correlationId); }
@Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println(" 回调id:" + correlationData); if (ack) { System.out.println("消息成功消费"); } else { System.out.println("消息消费失败:" + cause); } } }
|
修改Controller文件,添加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
| @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); } }
|