├── .gitignore ├── README.md ├── pom.xml ├── springboot-cache-redis ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── cn │ │ │ └── happyjava │ │ │ └── hello │ │ │ └── springboot │ │ │ └── springbootcacheredis │ │ │ ├── MockService.java │ │ │ ├── SpringbootCacheRedisApplication.java │ │ │ ├── TestController.java │ │ │ └── config │ │ │ └── RedisCacheConfig.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── cn │ └── happyjava │ └── hello │ └── springboot │ └── springbootcacheredis │ └── SpringbootCacheRedisApplicationTests.java ├── springboot-docker ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── docker │ │ └── Dockerfile │ ├── java │ │ └── cn │ │ │ └── happyjava │ │ │ └── springbootdocker │ │ │ └── SpringbootDockerApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── cn │ └── happyjava │ └── springbootdocker │ └── SpringbootDockerApplicationTests.java ├── springboot-initialise ├── .gitignore ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── cn │ │ │ │ └── happyjava │ │ │ │ └── springbootinitialise │ │ │ │ └── SpringbootInitialiseApplication.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── cn │ │ └── happyjava │ │ └── springbootinitialise │ │ └── SpringbootInitialiseApplicationTests.java └── 【快学springboot】1.快速创建springboot项目.md ├── springboot-security ├── .gitignore ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── cn │ │ │ │ └── happyjava │ │ │ │ └── springbootsecurity │ │ │ │ ├── SpringbootSecurityApplication.java │ │ │ │ ├── TestController.java │ │ │ │ └── config │ │ │ │ ├── AdminUser.java │ │ │ │ ├── AdminUserEntity.java │ │ │ │ ├── AuthFilter.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ └── UserDetailsServiceImpl.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── cn │ │ └── happyjava │ │ └── springbootsecurity │ │ └── SpringbootSecurityApplicationTests.java └── 「快学springboot」集成Spring Security实现鉴权功能.md ├── springboot-slimming ├── core │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── cn │ │ └── happyjava │ │ └── core │ │ ├── package-info.java │ │ └── utils │ │ └── TestUtils.java ├── pom.xml └── server │ ├── .gitignore │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── cn │ │ │ └── happyjava │ │ │ └── server │ │ │ ├── ServerApplication.java │ │ │ └── controller │ │ │ └── TestController.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── cn │ └── happyjava │ └── server │ └── ServerApplicationTests.java ├── springboot-springcache ├── .gitignore ├── 14【快学SpringBoot】快速上手好用方便的Spring Cache缓存框架.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── cn │ │ │ └── happyjava │ │ │ └── springbootspringcache │ │ │ ├── MockService.java │ │ │ ├── SpringbootSpringcacheApplication.java │ │ │ ├── TestController.java │ │ │ └── config │ │ │ └── CacheConfig.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── cn │ └── happyjava │ └── springbootspringcache │ └── SpringbootSpringcacheApplicationTests.java ├── springboot-springsecurity-jwt-mybatis-plus ├── .gitignore ├── SpringBoot+JWT+SpringSecurity+MybatisPlus实现Restful鉴权脚手架.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── cn │ │ │ └── happyjava │ │ │ └── springbootspringsecurityjwtmybatisplus │ │ │ ├── SpringbootSpringsecurityJwtMybatisPlusApplication.java │ │ │ ├── controller │ │ │ ├── AdminController.java │ │ │ └── AuthController.java │ │ │ ├── entity │ │ │ └── AdminEntity.java │ │ │ ├── exception │ │ │ └── ExceptionHandlerAdvice.java │ │ │ ├── mapper │ │ │ └── AdminMapper.java │ │ │ ├── security │ │ │ ├── AdminUser.java │ │ │ ├── AuthenticationEntryPoint.java │ │ │ ├── AuthenticationFilter.java │ │ │ ├── JwtHelp.java │ │ │ ├── SecurityConfig.java │ │ │ └── UserDetailsServiceImpl.java │ │ │ ├── service │ │ │ ├── AdminService.java │ │ │ └── impl │ │ │ │ └── AdminServiceImpl.java │ │ │ └── utils │ │ │ └── CookiesUtils.java │ └── resources │ │ ├── application.properties │ │ └── mappers │ │ └── adminMapper.xml │ └── test │ └── java │ └── cn │ └── happyjava │ └── springbootspringsecurityjwtmybatisplus │ └── SpringbootSpringsecurityJwtMybatisPlusApplicationTests.java └── transaction-propagation ├── .gitignore ├── Spring中的事务传播行为.md ├── pom.xml └── src ├── main ├── java │ └── cn │ │ └── happyjava │ │ └── transactionpropagation │ │ ├── TransactionPropagationApplication.java │ │ ├── entity │ │ └── UserEntity.java │ │ ├── repo │ │ └── UserRepo.java │ │ └── service │ │ ├── UserService.java │ │ └── UserService2.java └── resources │ └── application.properties └── test └── java └── cn └── happyjava └── transactionpropagation └── TransactionPropagationApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/tasks.xml 8 | .idea/**/dictionaries 9 | .idea/**/shelf 10 | 11 | # Sensitive or high-churn files 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.local.xml 15 | .idea/**/sqlDataSources.xml 16 | .idea/**/dynamic.xml 17 | .idea/**/uiDesigner.xml 18 | .idea/**/dbnavigator.xml 19 | 20 | # Gradle 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | cmake-build-release/ 27 | 28 | # Mongo Explorer plugin 29 | .idea/**/mongoSettings.xml 30 | 31 | # File-based project format 32 | *.iws 33 | 34 | # IntelliJ 35 | out/ 36 | 37 | # maven 38 | .mvn/ 39 | mvnw 40 | mvnw.cmd 41 | 42 | .idea/ 43 | housingfund.iml 44 | core/target/* 45 | client/target/* 46 | host/target/* 47 | target/ 48 | **/target/* 49 | *.dl 50 | *.iml 51 | *.pri 52 | *.pub 53 | *.exe 54 | *.ca 55 | *.priv 56 | certgen 57 | certFile.json 58 | hyperAccount.json 59 | key.json 60 | orgData.json 61 | account.conf 62 | *.prop 63 | *.csr 64 | *.originKey 65 | *.pem 66 | *.cnf 67 | *.cert 68 | !**/server.key 69 | !**/server.cert 70 | !**/client.pem 71 | !**/client.cert 72 | /data 73 | /logs/ 74 | **/logs/** 75 | *-dev.properties 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hello-springboot 2 | about learning Spring Boot via examples. Spring Boot 教程、SpringBoot代码示例 3 | 4 | 5 | 6 | Spring Boot教程大全,Spring Boot知识点样例代码。 7 | 8 | 9 | 10 | 我会不断补全SpringBoot的相关知识,希望对大家有所帮助~~~ 11 | 12 | 13 | 14 | 每一个项目的 README.md 都有讲解文章~ 15 | 16 | 17 | 18 | 本人博客地址:http://blog.happyjava.cn 19 | 20 | 本人掘金地址:https://juejin.im/user/5cc2895df265da03a630ddca 21 | 22 | 本人公众号:happyjavashare 23 | 24 | 本人头条号:Happyjava 25 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | pom 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.1.6.RELEASE 10 | 11 | 12 | cn.happyjava 13 | hello-springboot 14 | 0.0.1-SNAPSHOT 15 | hello-springboot 16 | example for spring boot 17 | 18 | 19 | springboot-initialise 20 | springboot-security 21 | springboot-springsecurity-jwt-mybatis-plus 22 | transaction-propagation 23 | springboot-docker 24 | springboot-springcache 25 | springboot-cache-redis 26 | springboot-slimming 27 | 28 | 29 | 30 | 11 31 | true 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | 55 | 56 | 57 | 58 | aliyun 59 | https://maven.aliyun.com/repository/public 60 | 61 | true 62 | 63 | 64 | false 65 | 66 | 67 | 68 | 69 | 70 | aliyun-plugin 71 | https://maven.aliyun.com/repository/public 72 | 73 | true 74 | 75 | 76 | false 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /springboot-cache-redis/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /springboot-cache-redis/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | cn.happyjava.hello.springboot 12 | springboot-cache-redis 13 | 0.0.1-SNAPSHOT 14 | springboot-cache-redis 15 | spring cache redis 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-devtools 30 | runtime 31 | true 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-configuration-processor 36 | true 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | true 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-cache 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-data-redis 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | 64 | 65 | 66 | 67 | 68 | 69 | aliyun 70 | https://maven.aliyun.com/repository/public 71 | 72 | true 73 | 74 | 75 | false 76 | 77 | 78 | 79 | 80 | 81 | aliyun-plugin 82 | https://maven.aliyun.com/repository/public 83 | 84 | true 85 | 86 | 87 | false 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /springboot-cache-redis/src/main/java/cn/happyjava/hello/springboot/springbootcacheredis/MockService.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.hello.springboot.springbootcacheredis; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | @Service 10 | public class MockService { 11 | 12 | /** 13 | * value 缓存的名字,与cacheName是一个东西 14 | * key 需要缓存的键,如果为空,则会根据参数自动拼接 15 | * 写法:SpEL 表达式 16 | */ 17 | @Cacheable(value = "listUsers", key = "#username") 18 | public List listUsers(String username) { 19 | System.out.println("执行了listUsers方法"); 20 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /springboot-cache-redis/src/main/java/cn/happyjava/hello/springboot/springbootcacheredis/SpringbootCacheRedisApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.hello.springboot.springbootcacheredis; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringbootCacheRedisApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringbootCacheRedisApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-cache-redis/src/main/java/cn/happyjava/hello/springboot/springbootcacheredis/TestController.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.hello.springboot.springbootcacheredis; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class TestController { 8 | 9 | private final MockService mockService; 10 | 11 | public TestController(MockService mockService) { 12 | this.mockService = mockService; 13 | } 14 | 15 | @GetMapping(value = "/listUsers") 16 | public Object listUsers(String username) { 17 | return mockService.listUsers(username); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /springboot-cache-redis/src/main/java/cn/happyjava/hello/springboot/springbootcacheredis/config/RedisCacheConfig.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.hello.springboot.springbootcacheredis.config; 2 | 3 | import org.springframework.cache.CacheManager; 4 | import org.springframework.cache.annotation.EnableCaching; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 8 | import org.springframework.data.redis.cache.RedisCacheManager; 9 | import org.springframework.data.redis.cache.RedisCacheWriter; 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; 11 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 12 | import org.springframework.data.redis.serializer.RedisSerializationContext; 13 | import org.springframework.data.redis.serializer.RedisSerializer; 14 | 15 | import java.time.Duration; 16 | 17 | /** 18 | * RedisConfig 19 | * 20 | * @author detectiveHLH 21 | * @date 2018-10-11 14:39 22 | **/ 23 | @Configuration 24 | @EnableCaching 25 | public class RedisCacheConfig { 26 | 27 | /** 28 | * 缓存管理器 29 | */ 30 | @Bean 31 | public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { 32 | //初始化一个RedisCacheWriter 33 | RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); 34 | //设置CacheManager的值序列化方式为json序列化 35 | RedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(); 36 | RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair 37 | .fromSerializer(jsonSerializer); 38 | RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig() 39 | .serializeValuesWith(pair); 40 | return new RedisCacheManager(redisCacheWriter, defaultCacheConfig); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /springboot-cache-redis/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.redis.host=127.0.0.1 2 | # spring.redis.password= 3 | spring.redis.port=6379 -------------------------------------------------------------------------------- /springboot-cache-redis/src/test/java/cn/happyjava/hello/springboot/springbootcacheredis/SpringbootCacheRedisApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.hello.springboot.springbootcacheredis; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringbootCacheRedisApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /springboot-docker/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /springboot-docker/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | cn.happyjava 12 | springboot-docker 13 | 0.0.1-SNAPSHOT 14 | springboot-docker 15 | SpringBoot:使用Docker构建、运行、发布一个SpringBoot应用 16 | 17 | 18 | 1.8 19 | true 20 | happy 21 | springboot 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-devtools 33 | runtime 34 | true 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-configuration-processor 39 | true 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | true 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | com.spotify 62 | docker-maven-plugin 63 | 1.0.0 64 | 65 | ${docker.image.prefix}/${project.artifactId} 66 | src/main/docker 67 | 68 | 69 | / 70 | ${project.build.directory} 71 | ${project.build.finalName}.jar 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | aliyun 83 | https://maven.aliyun.com/repository/public 84 | 85 | true 86 | 87 | 88 | false 89 | 90 | 91 | 92 | 93 | 94 | aliyun-plugin 95 | https://maven.aliyun.com/repository/public 96 | 97 | true 98 | 99 | 100 | false 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /springboot-docker/src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk 2 | VOLUME /tmp 3 | ADD springboot-docker-0.0.1-SNAPSHOT.jar app.jar 4 | ENTRYPOINT ["java","-jar","/app.jar"] -------------------------------------------------------------------------------- /springboot-docker/src/main/java/cn/happyjava/springbootdocker/SpringbootDockerApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootdocker; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @SpringBootApplication 9 | @RestController 10 | public class SpringbootDockerApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(SpringbootDockerApplication.class, args); 14 | } 15 | 16 | @GetMapping(value = "/test") 17 | public Object test() { 18 | return "Hello SpringBoot with Docker!"; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /springboot-docker/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /springboot-docker/src/test/java/cn/happyjava/springbootdocker/SpringbootDockerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootdocker; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringbootDockerApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /springboot-initialise/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /springboot-initialise/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | cn.happyjava 12 | springboot-initialise 13 | 0.0.1-SNAPSHOT 14 | springboot-initialise 15 | 快速创建springboot项目 16 | 17 | 18 | 11 19 | true 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | 29 | org.projectlombok 30 | lombok 31 | true 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /springboot-initialise/src/main/java/cn/happyjava/springbootinitialise/SpringbootInitialiseApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootinitialise; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringbootInitialiseApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringbootInitialiseApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-initialise/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /springboot-initialise/src/test/java/cn/happyjava/springbootinitialise/SpringbootInitialiseApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootinitialise; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringbootInitialiseApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /springboot-initialise/【快学springboot】1.快速创建springboot项目.md: -------------------------------------------------------------------------------- 1 | # 使用spring initialize工具快速创建springboot项目 2 | 3 | IDEA专业版默认集成了此工具,eclipse或者vs code等可以自行搜索安装。如果不希望安装此插件,也可直接通过官网创建spring boot项目,然后下载到本地即可。官网地址如下:https://start.spring.io/ 4 | 5 | # 在IDEA使用spring initialize工具 6 | 7 | 创建项目的时候选择spring initialize 8 | 9 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bdb1860403?w=640&h=594&f=jpeg&s=20990) 10 | 11 | 设置maven group,项目名,jdk版本,包名等信息 12 | 13 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bdb1949de7?w=640&h=594&f=jpeg&s=21939) 14 | 15 | 16 | 17 | 选择依赖 18 | 19 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bdb1b4a948?w=640&h=594&f=jpeg&s=23199) 20 | 21 | 22 | 23 | 这里我选择core下的Lombok和web下的web。 24 | 25 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bdb1c8b907?w=181&h=178&f=jpeg&s=2964) 26 | 27 | 28 | 29 | Lombok是一个简化pojo的通用方法的插件。Web是springboot的web核心依赖啦。然后一直下一步即可完成项目的创建了。 30 | 31 | # 自动引入的依赖 32 | 33 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bdb190240a?w=640&h=359&f=jpeg&s=31128) 34 | 35 | 36 | 37 | # 项目结构 38 | 39 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bdb1d2abf0?w=478&h=548&f=jpeg&s=23035) 40 | 41 | 42 | 43 | SpringbootApplication是springboot项目的启动类。SpringbootApplicationTests是springboot的单元测试的一个类,这里先不关注它。 44 | 45 | # 启动项目 46 | 47 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bddb979584?w=1240&h=356&f=jpeg&s=65537) 48 | 49 | 50 | 51 | 这里说明项目在8080端口启动了。通过浏览器访问该端口,出现以下页面说明springboot环境搭建成功 52 | 53 | ![img](https://user-gold-cdn.xitu.io/2019/6/12/16b4b8bde240f585?w=640&h=287&f=jpeg&s=22885) -------------------------------------------------------------------------------- /springboot-security/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /springboot-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | cn.happyjava 12 | springboot-security 13 | 0.0.1-SNAPSHOT 14 | springboot-security 15 | springboot继承spring security 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-test 30 | test 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-security 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-maven-plugin 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /springboot-security/src/main/java/cn/happyjava/springbootsecurity/SpringbootSecurityApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringbootSecurityApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringbootSecurityApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-security/src/main/java/cn/happyjava/springbootsecurity/TestController.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity; 2 | 3 | import cn.happyjava.springbootsecurity.config.AdminUser; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import javax.servlet.http.HttpSession; 10 | 11 | @RestController 12 | public class TestController { 13 | 14 | @Value("${name:happyjava}") 15 | private String name; 16 | 17 | @GetMapping(value = "/api/needlogin/test1") 18 | public Object test1(@AuthenticationPrincipal AdminUser adminUser) { 19 | return adminUser; 20 | } 21 | 22 | @GetMapping(value = "/api/notneedlogin/test2") 23 | public Object test2() { 24 | return name; 25 | } 26 | 27 | @GetMapping(value = "/api/notneedlogin/login") 28 | public Object login(HttpSession session) { 29 | // MOCK 模拟登陆 30 | session.setAttribute("username", "happyjava"); 31 | return "OK"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /springboot-security/src/main/java/cn/happyjava/springbootsecurity/config/AdminUser.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | import java.util.Collection; 10 | import java.util.Collections; 11 | 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class AdminUser implements UserDetails { 16 | 17 | private String username; 18 | 19 | @Override 20 | @SuppressWarnings("unchecked") 21 | public Collection getAuthorities() { 22 | // 这里可以定制化权限列表 23 | return Collections.EMPTY_LIST; 24 | } 25 | 26 | @Override 27 | public String getPassword() { 28 | return null; 29 | } 30 | 31 | @Override 32 | public String getUsername() { 33 | return username; 34 | } 35 | 36 | @Override 37 | public boolean isAccountNonExpired() { 38 | // 这里设置账号是否已经过期 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean isAccountNonLocked() { 44 | // 这里设置账号是否已经被锁定 45 | return true; 46 | } 47 | 48 | @Override 49 | public boolean isCredentialsNonExpired() { 50 | // 这里设置凭证是否过期 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean isEnabled() { 56 | // 是否可用 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /springboot-security/src/main/java/cn/happyjava/springbootsecurity/config/AdminUserEntity.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class AdminUserEntity { 11 | 12 | private Integer id; 13 | 14 | private String username; 15 | 16 | private String password; 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /springboot-security/src/main/java/cn/happyjava/springbootsecurity/config/AuthFilter.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.security.core.context.SecurityContextHolder; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.filter.OncePerRequestFilter; 10 | 11 | import javax.servlet.FilterChain; 12 | import javax.servlet.ServletException; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | @Component 18 | public class AuthFilter extends OncePerRequestFilter { 19 | 20 | @Autowired 21 | private UserDetailsServiceImpl userDetailsService; 22 | 23 | @Override 24 | protected void doFilterInternal(HttpServletRequest request, 25 | HttpServletResponse response, FilterChain filterChain) 26 | throws ServletException, IOException { 27 | String username = (String) request.getSession().getAttribute("username"); 28 | if (username != null && !"".equals(username)) { 29 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 30 | if (userDetails != null && userDetails.isEnabled()) { 31 | UsernamePasswordAuthenticationToken authenticationToken = 32 | new UsernamePasswordAuthenticationToken(userDetails, 33 | null, userDetails.getAuthorities()); 34 | // 把当前登陆用户放到上下文中 35 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails( 36 | request)); 37 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 38 | } else { 39 | // 用户不合法,清除上下文 40 | SecurityContextHolder.clearContext(); 41 | } 42 | } 43 | filterChain.doFilter(request, response); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /springboot-security/src/main/java/cn/happyjava/springbootsecurity/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 8 | import org.springframework.security.web.authentication.logout.LogoutFilter; 9 | 10 | /** 11 | * @author Happy 12 | */ 13 | @Configuration 14 | @EnableWebSecurity 15 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 16 | 17 | @Autowired 18 | private AuthFilter authFilter; 19 | 20 | @Override 21 | protected void configure(HttpSecurity httpSecurity) throws Exception { 22 | httpSecurity.antMatcher("/api/**") 23 | .addFilterBefore(authFilter, LogoutFilter.class) 24 | .authorizeRequests() 25 | .antMatchers("/api/notneedlogin/**").permitAll() 26 | .anyRequest().authenticated(); 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /springboot-security/src/main/java/cn/happyjava/springbootsecurity/config/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity.config; 2 | 3 | import org.springframework.security.core.userdetails.UserDetails; 4 | import org.springframework.security.core.userdetails.UserDetailsService; 5 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class UserDetailsServiceImpl implements UserDetailsService { 10 | 11 | @Override 12 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 13 | // MOCK 模拟从数据库 根据用户名查询用户 14 | AdminUserEntity adminUser = new AdminUserEntity(1, "happyjava", "123456"); 15 | // 构建 UserDetails 的实现类 => AdminUser 16 | return new AdminUser(adminUser.getUsername()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /springboot-security/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8888 2 | spring.security.userEntity.name=happyjava 3 | spring.security.userEntity.password=123456 -------------------------------------------------------------------------------- /springboot-security/src/test/java/cn/happyjava/springbootsecurity/SpringbootSecurityApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootsecurity; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringbootSecurityApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /springboot-security/「快学springboot」集成Spring Security实现鉴权功能.md: -------------------------------------------------------------------------------- 1 | ## Spring Security介绍 2 | 3 | Spring Security是Spring全家桶中的处理身份和权限问题的一员。Spring Security可以根据使用者的需要定制相关的角色身份和身份所具有的权限,完成黑名单操作、拦截无权限的操作等等。 4 | 5 | 本文将讲解Springboot中使用spring security。 6 | 7 | 8 | 9 | ## 引入依赖 10 | 11 | ```xml 12 | 13 | org.springframework.boot 14 | spring-boot-starter-security 15 | 16 | ``` 17 | 18 | 由于SpringBoot的自动配置的特性,引入了Spring Security依赖之后,已经默认帮我们配置了。不信,现在访问应用的根目录: 19 | 20 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43ce02a8a68?w=1495&h=557&f=png&s=57662) 21 | 22 | 居然跳到了一个登陆页面,我们什么都没有写呀。我们把spring security的依赖去掉,重启应用,然后再次访问根目录: 23 | 24 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43ce0115f52?w=804&h=349&f=png&s=28127) 25 | 26 | 这次,熟悉的页面出来了。其实这就是Springboot的魅力之处——自动配置。我们只是引入了Spring Security的依赖,就自动帮我们配置完了。 27 | 28 | 29 | 30 | ## 默认账号密码 31 | 32 | 我们可以通过SecurityProperties的源码查看,其默认账号是user,密码是启动的时候随机生成的,可以在日志里找到: 33 | 34 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43ce03ed406?w=821&h=397&f=png&s=41903) 35 | 36 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43ce0cfc49f?w=918&h=152&f=png&s=33808) 37 | 38 | 39 | 40 | ## 修改默认账号密码 41 | 42 | 我们可以通过配置文件修改Spring Security的默认账号密码,如下: 43 | 44 | ```properties 45 | spring.security.user.name=happyjava 46 | spring.security.user.password=123456 47 | ``` 48 | 49 | springboot1.x版本为: 50 | 51 | ```properties 52 | security.user.name=admin 53 | security.user.password=admin 54 | ``` 55 | 56 | 如果是一个普通的个人网站,如个人博客等。配置到这一步,已经可以充当一个登陆控制模块来使用了(当然还需要配置不需要登陆就可以访问的url)。 57 | 58 | 59 | 60 | ## 使用Spring Security定制化鉴权模块 61 | 62 | 虽然默认已经帮我们实现了一个简单的登陆认证模块,但是在实际开发中,这还是远远不够的。比如,我们有多个用户,有多中角色等等。一切,还是需要手动来开发。下面就一步一步来使用Spring Security: 63 | 64 | 65 | 66 | #### 配置不需要登陆的路径 67 | 68 | 我们当然需要配置不需要登陆就能访问的路径啦,比如:登陆接口(不然你怎么访问)。 69 | 70 | 71 | 72 | 有如下接口: 73 | 74 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43ce169db95?w=665&h=372&f=png&s=35640) 75 | 76 | 77 | 78 | 新建SecurityConfig,然后配置拦截的路径,配置路径白名单: 79 | 80 | ```java 81 | /** 82 | * @author Happy 83 | */ 84 | @Configuration 85 | @EnableWebSecurity 86 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 87 | 88 | @Bean 89 | public PasswordEncoder passwordEncoder() { 90 | return new BCryptPasswordEncoder(); 91 | } 92 | 93 | @Override 94 | protected void configure(HttpSecurity httpSecurity) throws Exception { 95 | httpSecurity.antMatcher("/api/**") 96 | .authorizeRequests() 97 | .antMatchers("/api/notneedlogin/**").permitAll() 98 | .anyRequest().authenticated(); 99 | } 100 | 101 | 102 | } 103 | ``` 104 | 105 | 排版乱请看图片版 106 | 107 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43ce18bc249?w=868&h=481&f=png&s=66462) 108 | 109 | 通过antMatchers("url").permitAll()方法,配置了/api/notneedlogin/**路径会被Spring Security放行。 110 | 111 | 112 | 113 | 启动项目验证下: 114 | 115 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d1aafd43f?w=811&h=395&f=png&s=28968) 116 | 117 | 需要登陆的接口拦截了返回403. 118 | 119 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d1abe93b6?w=531&h=196&f=png&s=9891) 120 | 121 | 配置了白名单的路径成功的获取到了数据。 122 | 123 | 其实,这个时候已经可以拿来当做一个普通个人网站的权限验证模块了,比如个人博客什么的。 124 | 125 | 126 | 127 | ## 抛弃默认配置,自定义鉴权方式 128 | 129 | 很多时候,我们都需要自定义鉴权方式啦。比如,我不用session来鉴权了,改用无状态的jwt方式(json web token)。这时候,我们就要对Spring Security进行定制化了。 130 | 131 | 首先,我们需要创建UserDetails的实现,这个就是Spring Security管理的用户权限对象: 132 | 133 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d1aef7aa6?w=522&h=226&f=png&s=19044) 134 | 135 | ```java 136 | @Data 137 | @AllArgsConstructor 138 | @NoArgsConstructor 139 | public class AdminUser implements UserDetails { 140 | 141 | private String username; 142 | 143 | @Override 144 | @SuppressWarnings("unchecked") 145 | public Collection getAuthorities() { 146 | // 这里可以定制化权限列表 147 | return Collections.EMPTY_LIST; 148 | } 149 | 150 | @Override 151 | public String getPassword() { 152 | return null; 153 | } 154 | 155 | @Override 156 | public String getUsername() { 157 | return username; 158 | } 159 | 160 | @Override 161 | public boolean isAccountNonExpired() { 162 | // 这里设置账号是否已经过期 163 | return true; 164 | } 165 | 166 | @Override 167 | public boolean isAccountNonLocked() { 168 | // 这里设置账号是否已经被锁定 169 | return true; 170 | } 171 | 172 | @Override 173 | public boolean isCredentialsNonExpired() { 174 | // 这里设置凭证是否过期 175 | return true; 176 | } 177 | 178 | @Override 179 | public boolean isEnabled() { 180 | // 是否可用 181 | return true; 182 | } 183 | } 184 | 185 | ``` 186 | 187 | 188 | 189 | 其次,我们还需要一个过滤器,拦截请求判断是否登陆,组装UserDetails: 190 | 191 | #### AuthFilter.class 192 | 193 | ```java 194 | @Component 195 | public class AuthFilter extends OncePerRequestFilter { 196 | 197 | @Autowired 198 | private UserDetailsServiceImpl userDetailsService; 199 | 200 | @Override 201 | protected void doFilterInternal(HttpServletRequest request, 202 | HttpServletResponse response, FilterChain filterChain) 203 | throws ServletException, IOException { 204 | String username = (String) request.getSession().getAttribute("username"); 205 | if (username != null && !"".equals(username)) { 206 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 207 | if (userDetails != null && userDetails.isEnabled()) { 208 | UsernamePasswordAuthenticationToken authenticationToken = 209 | new UsernamePasswordAuthenticationToken(userDetails, 210 | null, userDetails.getAuthorities()); 211 | // 把当前登陆用户放到上下文中 212 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails( 213 | request)); 214 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 215 | } else { 216 | // 用户不合法,清除上下文 217 | SecurityContextHolder.clearContext(); 218 | } 219 | } 220 | filterChain.doFilter(request, response); 221 | } 222 | 223 | } 224 | ``` 225 | 226 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d1cd6eabd?w=1147&h=695&f=png&s=114583) 227 | 228 | 229 | 230 | 这个过滤器里的userDetailsService,是Spring Security加载UserDetails的一个接口,代码如下: 231 | 232 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d2229ba48?w=994&h=174&f=png&s=21968) 233 | 234 | 它只有一个根据用户名加载当前权限用户的方法,我的实现如下: 235 | 236 | ```java 237 | @Service 238 | public class UserDetailsServiceImpl implements UserDetailsService { 239 | 240 | @Override 241 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 242 | // MOCK 模拟从数据库 根据用户名查询用户 243 | AdminUserEntity adminUser = new AdminUserEntity(1, "happyjava", "123456"); 244 | // 构建 UserDetails 的实现类 => AdminUser 245 | return new AdminUser(adminUser.getUsername()); 246 | } 247 | 248 | } 249 | ``` 250 | 251 | MOCK这里,需要用户真正的去实现,我这里只是演示使用。其中AdminUserEntity代码如下: 252 | 253 | ```java 254 | @Data 255 | @NoArgsConstructor 256 | @AllArgsConstructor 257 | public class AdminUserEntity { 258 | 259 | private Integer id; 260 | 261 | private String username; 262 | 263 | private String password; 264 | 265 | } 266 | ``` 267 | 268 | 269 | 270 | 到这里,已经完成了Spring Security的整套配置了。 271 | 272 | 273 | 274 | ## 测试 275 | 276 | 下面通过三个接口,测试配置是否生效: 277 | 278 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d34164dc7?w=866&h=567&f=png&s=76011) 279 | 280 | 增加了一个登陆接口,模拟真实用户登陆。其中,needLogin接口,使用了AuthenticationPrincipal注解来获取Spring Security中上下文的用户(这个实在Filter里面设置的)。 281 | 282 | 283 | 284 | 未登陆状态,访问test1接口: 285 | 286 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d493146c5?w=511&h=455&f=png&s=22510) 287 | 288 | 直接被拦截掉了,调用登录接口: 289 | 290 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d50397087?w=522&h=331&f=png&s=14070) 291 | 292 | 再次访问: 293 | 294 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d571fd27e?w=497&h=478&f=png&s=22927) 295 | 296 | 成功请求到了接口。 297 | 298 | 299 | 300 | ## 无状态jwt鉴权 301 | 302 | 本文演示的是使用session来完成鉴权的。使用session来做登录凭证,一个很大的痛点就是session共享问题。虽然springboot解决session共享就几个配置的问题,但终究还是得依赖别的服务,这是有状态的。 303 | 304 | 现在流行一种使用加密token的验证方式来鉴权,本人在项目中也是使用token的方式的(jjwt)。其主要做法就是,用户调用登陆接口,返回一串加密字符串,这串字符串里面包含用户名(username)等信息。用户后续的请求,把这个token带过来,通过解密的方式验证用户是否拥有权限。 305 | 306 | ![](https://user-gold-cdn.xitu.io/2019/6/21/16b7a43d5bbe6ef5?w=1027&h=298&f=png&s=64702) 307 | 308 | 309 | 310 | ## 总结 311 | 312 | 本文讲解了使用Spring Security来做鉴权框架,Spring Security配置起来还是挺繁琐的,但是配置完成之后,后续的获取上下文用户注解什么的,是真的方便。我把代码放到GitHub上,方便大家下载直接复制使用,地址如下: 313 | 314 | -------------------------------------------------------------------------------- /springboot-slimming/core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | springboot-slimming 7 | cn.happyjava 8 | 0.0.1 9 | 10 | 4.0.0 11 | 12 | core 13 | jar 14 | 15 | 16 | 11 17 | 11 18 | true 19 | 20 | 21 | 22 | 23 | org.apache.commons 24 | commons-lang3 25 | 3.11 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /springboot-slimming/core/src/main/java/cn/happyjava/core/package-info.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.core; -------------------------------------------------------------------------------- /springboot-slimming/core/src/main/java/cn/happyjava/core/utils/TestUtils.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.core.utils; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | 5 | import java.util.UUID; 6 | 7 | public class TestUtils { 8 | 9 | public static String getUUID() { 10 | return RandomStringUtils.random(100); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-slimming/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cn.happyjava 8 | springboot-slimming 9 | 0.0.1 10 | 11 | core 12 | server 13 | 14 | pom 15 | 16 | 17 | 11 18 | 11 19 | true 20 | 21 | 22 | -------------------------------------------------------------------------------- /springboot-slimming/server/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /springboot-slimming/server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.3 9 | 10 | 11 | cn.happyjava 12 | server 13 | 0.0.1-SNAPSHOT 14 | server 15 | jar 16 | Demo project for Spring Boot 17 | 18 | 11 19 | true 20 | 21 | 22 | 23 | cn.happyjava 24 | core 25 | 0.0.1 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-web 30 | 31 | 32 | 33 | org.projectlombok 34 | lombok 35 | true 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-maven-plugin 49 | 50 | ZIP 51 | 52 | 53 | core 54 | cn.happyjava 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /springboot-slimming/server/src/main/java/cn/happyjava/server/ServerApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ServerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ServerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-slimming/server/src/main/java/cn/happyjava/server/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.server.controller; 2 | 3 | import cn.happyjava.core.utils.TestUtils; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class TestController { 9 | 10 | @GetMapping(value = "/test") 11 | public Object test() { 12 | return TestUtils.getUUID(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /springboot-slimming/server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /springboot-slimming/server/src/test/java/cn/happyjava/server/ServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.server; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-springcache/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /springboot-springcache/14【快学SpringBoot】快速上手好用方便的Spring Cache缓存框架.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 缓存,在开发中是非常常用的。在高并发系统中,如果没有缓存,纯靠数据库来扛,那么数据库压力会非常大,搞不好还会出现宕机的情况。本篇文章,将会带大家学习Spring Cache缓存框架。 4 | 5 | ## 创建SpringBoot工程 6 | 7 | 通过Spring initialise快速创建SpringBoot工程,可以参考:http://blog.happyjava.cn/articles/7.html 8 | 9 | 10 | 11 | ## 引入依赖 12 | 13 | ```xml 14 | 15 | org.springframework.boot 16 | spring-boot-starter-cache 17 | 18 | ``` 19 | 20 | 21 | 22 | ## EnableCaching开启缓存 23 | 24 | ```java 25 | @Configuration 26 | @EnableCaching 27 | public class CacheConfig { 28 | 29 | /** 30 | * 默认就是这种配置,可以不写 31 | */ 32 | @Bean 33 | public CacheManager cacheManager() { 34 | return new ConcurrentMapCacheManager(); 35 | } 36 | 37 | } 38 | ``` 39 | 40 | 在SpringBoot中,默认就是ConcurrentMapCacheManager的缓存方式,不写也是可以的。这里可以通过CacheManager配置不同的缓存实现方式,比如redis,EHCACHE等。这个在下个章节再讲解。 41 | 42 | ConcurrentMapCacheManager还有一个不定参数的重载构造方法, 43 | 44 | ![](http://file.happyjava.cn/picgo/20190721233127.png) 45 | 46 | 它接收的是cacheName入参,如果设置了缓存名字,那么后续的方法就只能使用在这里设置的缓存,否则会抛出异常。如果是无参构造方法,那么它是一个可变的缓存管理器。 47 | 48 | ![](http://file.happyjava.cn/picgo/20190721233454.png) 49 | 50 | 51 | 52 | ## Cacheable注解 53 | 54 | Cacheable注解是用来设置缓存的。常用注解如下: 55 | 56 | #### value或者cacheNames 57 | 58 | 指定使用的缓存,其实也就是上面说到的cacheName。如果是无参的ConcurrentMapCacheManager,那么这里可以根据自己的用途等因素自定义即可。 59 | 60 | 61 | 62 | #### key 63 | 64 | 缓存的key,就跟Map一样,是操作缓存的键。 65 | 66 | 这里接受的是 **SpEL**表达式。关于SpEL表达式,下面做个简要说明: 67 | 68 | 有如下方法: 69 | 70 | ```java 71 | @CachePut(value = "listUsers", key = "#username") 72 | public List updateCache(String username) { 73 | System.out.println("执行了updateCache方法"); 74 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 75 | } 76 | ``` 77 | 78 | 可以通过 #paramName 的方式获得入参 79 | 80 | 还有一个是root对象,用法如下: 81 | 82 | ```java 83 | @CachePut(value = "listUsers", key = "#root.methodName+#username") 84 | public List updateCache(String username) { 85 | System.out.println("执行了updateCache方法"); 86 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 87 | } 88 | ``` 89 | 90 | 其实没必要记住,通过IDEA的智能提示灵活使用即可: 91 | 92 | ![](http://file.happyjava.cn/picgo/20190721234614.png) 93 | 94 | 各个参数代表的意思,相信大家一目了然。 95 | 96 | 97 | 98 | #### condition 99 | 100 | 缓存控制条件,如果使用了该字段,那么只有结果为true时,才会缓存结果。 101 | 102 | ```java 103 | @Cacheable(value = "listUsers", condition = "#username.length()>5") 104 | public List listUsers(String username) { 105 | System.out.println("执行了listUsers方法"); 106 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 107 | } 108 | ``` 109 | 110 | 下面贴出一个设置缓存的例子: 111 | 112 | ```java 113 | @Cacheable(value = "listUsers", key = "#root.methodName+#username", condition = "#username.equals('Happyjava')") 114 | public List listUsers(String username) { 115 | System.out.println("执行了listUsers方法"); 116 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 117 | } 118 | ``` 119 | 120 | 只有当入参username等于"Happyjava"的时候,才会缓存结果,可以通过是否多次打印:"执行了listUsers方法"来判断。 121 | 122 | 123 | 124 | ## CachePut注解 125 | 126 | 相信HTTP协议熟悉的朋友一看名字就知道这个注解是干嘛用的了。我们可以通过CachePut注解来更新缓存。其常用注解与Cacheable是一致的。下面给出一个更新缓存的例子: 127 | 128 | ```java 129 | @CachePut(value = "listUsers", key = "#username", condition = "#username.equals('Happyjava')") 130 | public List updateCache(String username) { 131 | System.out.println("执行了updateCache方法"); 132 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 133 | } 134 | ``` 135 | 136 | 我们可以写一个value和key与之对应的Cacheable注解进行测试: 137 | 138 | ```java 139 | @Cacheable(value = "listUsers", key = "#username", condition = "#username.equals('Happyjava')") 140 | public List listUsers(String username) { 141 | System.out.println("执行了listUsers方法"); 142 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 143 | } 144 | ``` 145 | 146 | 测试预期的结果是:调用了CachePut方法后,Cacheable方法会返回一个新的结果。 147 | 148 | 149 | 150 | ## CacheEvict注解 151 | 152 | 这是一个删除注解。常用参数除了上面两个注解列出的三个之外,还有一个allEntries,这是一个布尔类型的参数,默认为false,其意思是“是否删除所有缓存”。在false的情况下,只是删除与key相对应的缓存,如果为true,则会删除所有缓存(当然是对应的value下的)。 153 | 154 | ```java 155 | @CacheEvict(value = "listUsers", key = "#username") 156 | public void deleteCache(String username) { 157 | System.out.println("执行了deleteCache方法"); 158 | } 159 | 160 | @CacheEvict(value = "listUsers", allEntries = true) 161 | public void deleteAllCache() { 162 | 163 | } 164 | ``` 165 | 166 | 167 | 168 | ## 总结 169 | 170 | 当然,这只是Spring cache的一个快速上手示例,其实我们更多时候不是这么使用的。在实际项目中,更多是配合redis进行使用的,这个放在下篇文章讲解吧(其实也就是一些配置的事情) 171 | 172 | 173 | 174 | ## 参考代码 175 | 176 | https://github.com/Happy4Java/hello-springboot -------------------------------------------------------------------------------- /springboot-springcache/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | cn.happyjava 12 | springboot-springcache 13 | 0.0.1-SNAPSHOT 14 | springboot-springcache 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-devtools 30 | runtime 31 | true 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-configuration-processor 36 | true 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | true 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-cache 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-maven-plugin 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /springboot-springcache/src/main/java/cn/happyjava/springbootspringcache/MockService.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringcache; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.cache.annotation.CacheEvict; 5 | import org.springframework.cache.annotation.CachePut; 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | @Service 14 | public class MockService { 15 | 16 | /** 17 | * value 缓存的名字,与cacheName是一个东西 18 | * key 需要缓存的键,如果为空,则会根据参数自动拼接 19 | * 写法:SpEL 表达式 20 | */ 21 | @Cacheable(value = "listUsers", key = "#username", condition = "#username.equals('Happyjava')") 22 | public List listUsers(String username) { 23 | System.out.println("执行了listUsers方法"); 24 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 25 | } 26 | 27 | @CachePut(value = "listUsers", key = "#username", condition = "#username.equals('Happyjava')") 28 | public List updateCache(String username) { 29 | System.out.println("执行了updateCache方法"); 30 | return Arrays.asList("Happyjava", "Hello-SpringBoot", System.currentTimeMillis() + ""); 31 | } 32 | 33 | @CacheEvict(value = "listUsers", key = "#username") 34 | public void deleteCache(String username) { 35 | System.out.println("执行了deleteCache方法"); 36 | } 37 | 38 | @CacheEvict(value = "listUsers", allEntries = true) 39 | public void deleteAllCache() { 40 | 41 | } 42 | 43 | /** 44 | * 符合condition 条件的才会缓存 45 | */ 46 | @Cacheable(value = "listUsers", condition = "#name.equals('Happyjava')") 47 | public List getUserByName(String name) { 48 | return Arrays.asList(name, UUID.randomUUID().toString()); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /springboot-springcache/src/main/java/cn/happyjava/springbootspringcache/SpringbootSpringcacheApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringcache; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringbootSpringcacheApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringbootSpringcacheApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-springcache/src/main/java/cn/happyjava/springbootspringcache/TestController.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringcache; 2 | 3 | import cn.happyjava.springbootspringcache.config.CacheConfig; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class TestController { 9 | 10 | private final MockService mockService; 11 | 12 | public TestController(MockService mockService) { 13 | this.mockService = mockService; 14 | } 15 | 16 | @GetMapping(value = "/listUsers") 17 | public Object listUsers(String username) { 18 | return mockService.listUsers(username); 19 | } 20 | 21 | @GetMapping(value = "/updateCache") 22 | public Object updateCache(String username) { 23 | return mockService.updateCache(username); 24 | } 25 | 26 | @GetMapping(value = "/deleteCache") 27 | public Object deleteCache(String username) { 28 | mockService.deleteCache(username); 29 | return "OK"; 30 | } 31 | 32 | @GetMapping(value = "/deleteAllCache") 33 | public Object deleteAllCache() { 34 | mockService.deleteAllCache(); 35 | return "OK"; 36 | } 37 | 38 | @GetMapping(value = "/getUserByName") 39 | public Object getUserByName(String name) { 40 | return mockService.getUserByName(name); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /springboot-springcache/src/main/java/cn/happyjava/springbootspringcache/config/CacheConfig.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringcache.config; 2 | 3 | import org.springframework.cache.CacheManager; 4 | import org.springframework.cache.annotation.EnableCaching; 5 | import org.springframework.cache.concurrent.ConcurrentMapCacheManager; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * @author happy 11 | */ 12 | @Configuration 13 | @EnableCaching 14 | public class CacheConfig { 15 | 16 | /** 17 | * 默认就是这种配置,可以不写 18 | */ 19 | @Bean 20 | public CacheManager cacheManager() { 21 | return new ConcurrentMapCacheManager(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /springboot-springcache/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /springboot-springcache/src/test/java/cn/happyjava/springbootspringcache/SpringbootSpringcacheApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringcache; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringbootSpringcacheApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/SpringBoot+JWT+SpringSecurity+MybatisPlus实现Restful鉴权脚手架.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | JWT(json web token)的无状态鉴权方式,越来越流行。配合SpringSecurity+SpringBoot,可以实现优雅的鉴权功能。 4 | 5 | 关于SpringBoot+ Security的讲解,可以参考我之前的文章:https://www.toutiao.com/i6704647082659021319/ 6 | 7 | 为了减少重复造轮子的工作量,方便大家复制和参考,我把一个完整的SpringBoot+JWT+SpringSecurity+Mybatis-Plus开发的项目,放到本人的github上,方便自己的同时也方便他人。 8 | 9 | 10 | 11 | ## 源码获取 12 | 13 | github地址,参考文末即可。 14 | 15 | 16 | 17 | ## 项目实现的功能 18 | 19 | 1、整合了好用方便的Mybatis-plus 20 | 21 | 2、整合了JWT 22 | 23 | 3、整合了Spring Security 24 | 25 | 26 | 27 | ## 简单演示 28 | 29 | 配置拦截和放行的路径 30 | 31 | ![](http://file.happyjava.cn/picgo/20190703235944.png) 32 | 33 | 34 | 35 | #### 未登录请求 36 | 37 | ![](http://file.happyjava.cn/picgo/20190704000049.png) 38 | 39 | 会被拦截返回401。这个返回的内容用户可以自定义即可 40 | 41 | 42 | 43 | #### 登录 44 | 45 | ![](http://file.happyjava.cn/picgo/20190704000137.png) 46 | 47 | 48 | 49 | #### 登录成功后访问需要登录的接口 50 | 51 | ![](http://file.happyjava.cn/picgo/20190704000215.png) 52 | 53 | 这里成功请求到了数据。 54 | 55 | 56 | 57 | ## 通过注解获取当前 登录的用户 58 | 59 | ![](http://file.happyjava.cn/picgo/20190704000259.png) 60 | 61 | ![](http://file.happyjava.cn/picgo/20190704000324.png) 62 | 63 | 64 | 65 | ## 项目部署 66 | 67 | #### 数据库准备 68 | 69 | 建立数据库test,建表如下: 70 | 71 | ``` 72 | CREATE TABLE `admin` ( 73 | `id` int(11) NOT NULL AUTO_INCREMENT, 74 | `username` varchar(255) NOT NULL, 75 | `password` varchar(255) NOT NULL, 76 | PRIMARY KEY (`id`) 77 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 78 | ``` 79 | 80 | 建表之后,自行插入用户名密码。 81 | 82 | 83 | 84 | ## 修改配置文件applicatoin.properties 85 | 86 | 把数据密码等配置修改正确 87 | 88 | ```properties 89 | spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai 90 | spring.datasource.username=root 91 | spring.datasource.password=123456 92 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 93 | # 需要手写mapper打开此配置 94 | mybatis-plus.mapper-locations=classpath:mappers/*.xml 95 | # jwt的密钥 96 | jwt.secret.key=happyjava1234214214asfasfasfasdf 97 | # jwt过期时间 98 | jwt.token.expired=360000 99 | ``` 100 | 101 | 也可自定义jwt的加密密钥和token过期时间 102 | 103 | 104 | 105 | ## 启动项目 106 | 107 | 启动项目即可通过接口进行测试 108 | 109 | 110 | 111 | ## 源码地址 112 | 113 | https://github.com/Happy4Java/hello-springboot 114 | 115 | ![](http://file.happyjava.cn/picgo/20190704000731.png) 116 | 117 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | cn.happyjava 12 | springboot-springsecurity-jwt-mybatis-plus 13 | 0.0.1-SNAPSHOT 14 | springboot-springsecurity-jwt-mybatis-plus 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-security 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-devtools 33 | runtime 34 | true 35 | 36 | 37 | mysql 38 | mysql-connector-java 39 | runtime 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-configuration-processor 44 | true 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | true 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | org.springframework.security 58 | spring-security-test 59 | test 60 | 61 | 62 | io.jsonwebtoken 63 | jjwt-api 64 | 0.10.5 65 | 66 | 67 | io.jsonwebtoken 68 | jjwt-impl 69 | 0.10.5 70 | runtime 71 | 72 | 73 | io.jsonwebtoken 74 | jjwt-jackson 75 | 0.10.5 76 | runtime 77 | 78 | 79 | com.baomidou 80 | mybatis-plus 81 | 3.1.2 82 | 83 | 84 | com.baomidou 85 | mybatis-plus-boot-starter 86 | 3.1.1 87 | 88 | 89 | org.apache.commons 90 | commons-lang3 91 | 92 | 93 | 94 | 95 | 96 | 97 | org.springframework.boot 98 | spring-boot-maven-plugin 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-compiler-plugin 103 | 104 | 11 105 | 11 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | aliyun 114 | https://maven.aliyun.com/repository/public 115 | 116 | true 117 | 118 | 119 | false 120 | 121 | 122 | 123 | 124 | 125 | aliyun-plugin 126 | https://maven.aliyun.com/repository/public 127 | 128 | true 129 | 130 | 131 | false 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/SpringbootSpringsecurityJwtMybatisPlusApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author happy 8 | */ 9 | @SpringBootApplication 10 | public class SpringbootSpringsecurityJwtMybatisPlusApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(SpringbootSpringsecurityJwtMybatisPlusApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/controller/AdminController.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | * @author happy 9 | */ 10 | @RestController 11 | @RequestMapping(value = "/api/v1/admin") 12 | public class AdminController { 13 | 14 | @GetMapping(value = "/test") 15 | public Object test() { 16 | return "OK"; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.controller; 2 | 3 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.security.AdminUser; 4 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.service.AdminService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | /** 12 | * @author happy 13 | */ 14 | @RestController 15 | @RequestMapping(value = "/api/v1/auth") 16 | public class AuthController { 17 | 18 | private final AdminService adminService; 19 | 20 | public AuthController(AdminService adminService) { 21 | this.adminService = adminService; 22 | } 23 | 24 | @PostMapping(value = "/login") 25 | public Object login(@RequestParam String username, @RequestParam String password, HttpServletResponse response) { 26 | return adminService.login(response, username, password); 27 | } 28 | 29 | @GetMapping(value = "/current") 30 | public Object currentUser(@AuthenticationPrincipal AdminUser adminUser) { 31 | return adminUser; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/entity/AdminEntity.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author happy 8 | */ 9 | @Data 10 | @TableName(value = "admin") 11 | public class AdminEntity { 12 | 13 | private Integer id; 14 | 15 | private String username; 16 | 17 | private String password; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/exception/ExceptionHandlerAdvice.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.exception; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.authentication.InsufficientAuthenticationException; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | 12 | 13 | /** 14 | * @author Happy 15 | */ 16 | @Slf4j 17 | @RestControllerAdvice 18 | public class ExceptionHandlerAdvice { 19 | 20 | /** 21 | * 权限不足 22 | */ 23 | @ExceptionHandler(InsufficientAuthenticationException.class) 24 | public ResponseEntity handleInsufficientAuthenticationException(InsufficientAuthenticationException e, 25 | HttpServletRequest request) { 26 | // TODO 处理未登录的请求 27 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("未登录"); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/mapper/AdminMapper.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.mapper; 2 | 3 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.entity.AdminEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Select; 7 | import org.springframework.stereotype.Component; 8 | 9 | 10 | /** 11 | * 使用Component注解主要是为了不然IDEA报错,其实不用也是可以的 12 | * 13 | * @author happy 14 | */ 15 | @Component 16 | @Mapper 17 | public interface AdminMapper extends BaseMapper { 18 | 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/security/AdminUser.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.security; 2 | 3 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.entity.AdminEntity; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.context.annotation.Scope; 8 | import org.springframework.security.core.GrantedAuthority; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | 11 | import java.util.Collection; 12 | 13 | /** 14 | * @author Happy 15 | */ 16 | @Data 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Scope("session") 20 | public class AdminUser implements UserDetails { 21 | 22 | private String username; 23 | 24 | @Override 25 | public Collection getAuthorities() { 26 | return null; 27 | } 28 | 29 | @Override 30 | public String getPassword() { 31 | return null; 32 | } 33 | 34 | @Override 35 | public String getUsername() { 36 | return username; 37 | } 38 | 39 | @Override 40 | public boolean isAccountNonExpired() { 41 | return true; 42 | } 43 | 44 | @Override 45 | public boolean isAccountNonLocked() { 46 | return true; 47 | } 48 | 49 | @Override 50 | public boolean isCredentialsNonExpired() { 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean isEnabled() { 56 | return true; 57 | } 58 | 59 | public static AdminUser parse(AdminEntity adminEntity) { 60 | return new AdminUser(adminEntity.getUsername()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/security/AuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.security; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | import org.springframework.security.core.AuthenticationException; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.servlet.HandlerExceptionResolver; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author Happy 15 | */ 16 | @Component 17 | public class AuthenticationEntryPoint implements org.springframework.security.web.AuthenticationEntryPoint { 18 | 19 | private final HandlerExceptionResolver resolver; 20 | 21 | public AuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) { 22 | this.resolver = resolver; 23 | } 24 | 25 | @Override 26 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { 27 | resolver.resolveException(request, response, null, e); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/security/AuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.security; 2 | 3 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.utils.CookiesUtils; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 12 | import org.springframework.web.filter.OncePerRequestFilter; 13 | 14 | import javax.servlet.FilterChain; 15 | import javax.servlet.ServletException; 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.io.IOException; 19 | 20 | /** 21 | * @author Happy 22 | */ 23 | @Slf4j 24 | public class AuthenticationFilter extends OncePerRequestFilter { 25 | 26 | @Autowired 27 | private UserDetailsServiceImpl userDetailsService; 28 | 29 | @Autowired 30 | private JwtHelp jwtHelp; 31 | 32 | @Override 33 | protected void doFilterInternal( 34 | HttpServletRequest request, 35 | HttpServletResponse response, 36 | FilterChain chain) throws ServletException, IOException { 37 | 38 | String token = CookiesUtils.getValue(request, "token"); 39 | String username = null; 40 | if (StringUtils.isNotBlank(token)) { 41 | username = jwtHelp.getUsernameFromToken(token); 42 | } 43 | if (!StringUtils.isBlank(username)) { 44 | try { 45 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 46 | if (userDetails.isEnabled()) { 47 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( 48 | userDetails, null, userDetails.getAuthorities()); 49 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( 50 | request)); 51 | SecurityContextHolder.getContext().setAuthentication(authentication); 52 | } else { 53 | SecurityContextHolder.clearContext(); 54 | } 55 | } catch (UsernameNotFoundException e) { 56 | log.error("username【{}】 not found", username, e); 57 | } 58 | } else { 59 | SecurityContextHolder.clearContext(); 60 | } 61 | chain.doFilter(request, response); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/security/JwtHelp.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.security; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.security.Keys; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.security.authentication.InsufficientAuthenticationException; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.PostConstruct; 12 | import java.security.Key; 13 | import java.util.Date; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * @author Happy 19 | */ 20 | @Component 21 | public class JwtHelp { 22 | 23 | private static final String CLAIM_KEY_USERNAME = "username"; 24 | 25 | private static final String CLAIM_KEY_CREATED = "createTime"; 26 | 27 | @Value("${jwt.secret.key}") 28 | private String secretKey; 29 | 30 | @Value("${jwt.token.expired}") 31 | private Long expired; 32 | 33 | private Key key; 34 | 35 | @PostConstruct 36 | public void init() { 37 | key = Keys.hmacShaKeyFor(secretKey.getBytes()); 38 | } 39 | 40 | public String generateToken(UserDetails userDetails) { 41 | Map claims = new HashMap<>(5); 42 | claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); 43 | claims.put(CLAIM_KEY_CREATED, new Date()); 44 | return generateToken(claims); 45 | } 46 | 47 | private String generateToken(Map claims) { 48 | return Jwts.builder() 49 | .setClaims(claims) 50 | .setExpiration(new Date(System.currentTimeMillis() + expired)) 51 | .signWith(key) 52 | .compact(); 53 | } 54 | 55 | 56 | /** 57 | * 获取过期时间戳 58 | * 59 | * @param token 60 | * @return 61 | */ 62 | public long getExpirationTime(String token) { 63 | final Claims claims = getClaimsFromToken(token); 64 | if (claims == null) { 65 | return 0; 66 | } 67 | return claims.getExpiration().getTime(); 68 | } 69 | 70 | /** 71 | * 从token中获取username,如果过期则抛出异常 72 | * 73 | * @param token token 74 | * @return username 75 | */ 76 | public String getUsernameFromToken(String token) { 77 | try { 78 | final Claims claims = getClaimsFromToken(token); 79 | if (claims == null) { 80 | return null; 81 | } 82 | Date expiration = claims.getExpiration(); 83 | if (new Date().after(expiration)) { 84 | throw new InsufficientAuthenticationException("session timeout"); 85 | } 86 | return (String) claims.get(CLAIM_KEY_USERNAME); 87 | } catch (Exception e) { 88 | return null; 89 | } 90 | } 91 | 92 | private Claims getClaimsFromToken(String token) { 93 | Claims claims; 94 | try { 95 | claims = Jwts.parser() 96 | .setSigningKey(key) 97 | .parseClaimsJws(token) 98 | .getBody(); 99 | } catch (Exception e) { 100 | return null; 101 | } 102 | return claims; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.security.config.http.SessionCreationPolicy; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.web.authentication.logout.LogoutFilter; 15 | import org.springframework.web.cors.CorsConfiguration; 16 | import org.springframework.web.cors.CorsConfigurationSource; 17 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 18 | 19 | /** 20 | * @author Loger 21 | */ 22 | @Configuration 23 | @EnableWebSecurity 24 | @EnableGlobalMethodSecurity(prePostEnabled = true) 25 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 26 | 27 | @Bean 28 | public PasswordEncoder passwordEncoder() { 29 | return new BCryptPasswordEncoder(); 30 | } 31 | 32 | @Autowired 33 | private AuthenticationEntryPoint unauthorizedHandler; 34 | 35 | @Override 36 | protected void configure(HttpSecurity httpSecurity) throws Exception { 37 | httpSecurity 38 | .antMatcher("/api/**") 39 | .addFilterBefore(authenticationFilterBean(), LogoutFilter.class) 40 | .authorizeRequests() 41 | // 配置不需要登录即可访问的路径 42 | .antMatchers("/api/v1/auth/login").permitAll() 43 | .anyRequest().authenticated() 44 | .and() 45 | .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) 46 | .and() 47 | .cors() 48 | .and() 49 | .httpBasic().disable() 50 | .csrf().disable() 51 | .formLogin().disable() 52 | .logout().disable(); 53 | httpSecurity.headers().cacheControl(); 54 | httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 55 | } 56 | 57 | @Bean 58 | public CorsConfigurationSource corsConfigurationSource() { 59 | CorsConfiguration configuration = new CorsConfiguration(); 60 | configuration.setAllowCredentials(true); 61 | configuration.addAllowedOrigin(CorsConfiguration.ALL); 62 | configuration.addAllowedHeader(CorsConfiguration.ALL); 63 | configuration.addAllowedMethod(CorsConfiguration.ALL); 64 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 65 | source.registerCorsConfiguration("/**", configuration); 66 | return source; 67 | } 68 | 69 | @Bean 70 | public FilterRegistrationBean registerAuthenticationFilter() { 71 | FilterRegistrationBean bean = new FilterRegistrationBean<>(); 72 | bean.setFilter(authenticationFilterBean()); 73 | bean.setEnabled(false); 74 | return bean; 75 | } 76 | 77 | @Bean 78 | public AuthenticationFilter authenticationFilterBean() { 79 | return new AuthenticationFilter(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/security/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.security; 2 | 3 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.entity.AdminEntity; 4 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.mapper.AdminMapper; 5 | import com.baomidou.mybatisplus.core.conditions.Wrapper; 6 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 7 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 12 | import org.springframework.stereotype.Service; 13 | 14 | /** 15 | * @author Loger 16 | */ 17 | @Service 18 | public class UserDetailsServiceImpl implements UserDetailsService { 19 | 20 | private final AdminMapper adminMapper; 21 | 22 | public UserDetailsServiceImpl(AdminMapper adminMapper) { 23 | this.adminMapper = adminMapper; 24 | } 25 | 26 | @Override 27 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 28 | QueryWrapper queryWrapper = Wrappers.query() 29 | .eq("username", username); 30 | AdminEntity adminEntity = adminMapper.selectOne(queryWrapper); 31 | if (adminEntity == null) { 32 | throw new UsernameNotFoundException(username + " not found"); 33 | } 34 | return AdminUser.parse(adminEntity); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/service/AdminService.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.service; 2 | 3 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.security.AdminUser; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | /** 8 | * @author happy 9 | */ 10 | public interface AdminService { 11 | 12 | public AdminUser login(HttpServletResponse response, String username, String password); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/service/impl/AdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.service.impl; 2 | 3 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.entity.AdminEntity; 4 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.mapper.AdminMapper; 5 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.security.AdminUser; 6 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.security.JwtHelp; 7 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.service.AdminService; 8 | import cn.happyjava.springbootspringsecurityjwtmybatisplus.utils.CookiesUtils; 9 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 10 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author happy 21 | */ 22 | @Service 23 | public class AdminServiceImpl implements AdminService { 24 | 25 | private final AdminMapper adminMapper; 26 | 27 | private final JwtHelp jwtHelp; 28 | 29 | public AdminServiceImpl(AdminMapper adminMapper, 30 | JwtHelp jwtHelp) { 31 | this.adminMapper = adminMapper; 32 | this.jwtHelp = jwtHelp; 33 | } 34 | 35 | @Override 36 | public AdminUser login(HttpServletResponse response, String username, String password) { 37 | // QueryWrapper queryWrapper = Wrappers.emptyWrapper() 38 | // .eq("username", username) 39 | // .eq("password", password); 40 | QueryWrapper queryWrapper = Wrappers.query() 41 | .eq("username", username) 42 | .eq("password", password); 43 | AdminEntity adminEntity = adminMapper.selectOne(queryWrapper); 44 | if (adminEntity == null) { 45 | // 采用异常流处理业务逻辑 46 | throw new RuntimeException("用户名密码错误"); 47 | } 48 | AdminUser adminUser = AdminUser.parse(adminEntity); 49 | String token = jwtHelp.generateToken(adminUser); 50 | // 这里不一定是采用cookie,如果是移动端,可以直接返回 51 | CookiesUtils.setCookies(response, "token", token); 52 | return adminUser; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/utils/CookiesUtils.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus.utils; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletResponse; 6 | import java.io.UnsupportedEncodingException; 7 | import java.net.URLDecoder; 8 | import java.net.URLEncoder; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.Objects; 11 | 12 | /** 13 | * @author Happy 14 | */ 15 | public class CookiesUtils { 16 | 17 | /** 18 | * 设置cookie 19 | * 20 | * @param expired 有效时间(s) 21 | */ 22 | public static void setCookies(HttpServletResponse response, String name, String value, int expired) throws UnsupportedEncodingException { 23 | value = URLEncoder.encode(value, StandardCharsets.UTF_8); 24 | Cookie cookies = new Cookie(name, value); 25 | cookies.setMaxAge(expired); 26 | cookies.setPath("/"); 27 | response.addCookie(cookies); 28 | } 29 | 30 | /** 31 | * 设置cookie 32 | */ 33 | public static void setCookies(HttpServletResponse response, String name, String value) { 34 | value = URLEncoder.encode(value, StandardCharsets.UTF_8); 35 | Cookie cookies = new Cookie(name, value); 36 | cookies.setPath("/"); 37 | response.addCookie(cookies); 38 | } 39 | 40 | /** 41 | * 获得cookie 42 | */ 43 | public static String getValue(HttpServletRequest request, String name) throws UnsupportedEncodingException { 44 | Cookie[] cookies = request.getCookies(); 45 | if (cookies == null || cookies.length == 0) { 46 | return null; 47 | } 48 | for (Cookie cookie : cookies) { 49 | if (Objects.equals(cookie.getName(), name)) { 50 | return URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8); 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyjava007/hello-springboot/7e7d5ced41a08defb9b3342ebe2485ea5cd9b3ee/springboot-springsecurity-jwt-mybatis-plus/src/main/resources/application.properties -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/main/resources/mappers/adminMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /springboot-springsecurity-jwt-mybatis-plus/src/test/java/cn/happyjava/springbootspringsecurityjwtmybatisplus/SpringbootSpringsecurityJwtMybatisPlusApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.springbootspringsecurityjwtmybatisplus; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringbootSpringsecurityJwtMybatisPlusApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /transaction-propagation/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /transaction-propagation/Spring中的事务传播行为.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 在开发中,相信大家都使用过Spring的事务管理功能。那么,你是否有了解过,Spring的事务传播行为呢? 4 | 5 | Spring中,有7种类型的事务传播行为。事务传播行为是Spring框架提供的一种事务管理方式,它不是数据库提供的。不知道大家是否听说过“不要在service事务方法中嵌套事务方法,这样会提交多个事务”的说法,其实这是不准确的。了解了事务传播行为之后,相信你就会明白! 6 | 7 | 8 | 9 | ## Spring中七种事务传播行为 10 | 11 | 事务的传播行为,默认值为 Propagation.REQUIRED。可以手动指定其他的事务传播行为,如下: 12 | 13 | - Propagation.REQUIRED 14 | 15 | 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。 16 | 17 | - Propagation.SUPPORTS 18 | 19 | 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。 20 | 21 | - Propagation.MANDATORY 22 | 23 | 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。 24 | 25 | - Propagation.REQUIRES_NEW 26 | 27 | 重新创建一个新的事务,如果当前存在事务,延缓当前的事务。 28 | 29 | - Propagation.NOT_SUPPORTED 30 | 31 | 以非事务的方式运行,如果当前存在事务,暂停当前的事务。 32 | 33 | - Propagation.NEVER 34 | 35 | 以非事务的方式运行,如果当前存在事务,则抛出异常。 36 | 37 | - Propagation.NESTED 38 | 39 | 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。 40 | 41 | 42 | 43 | ## 准备工作 44 | 45 | 数据库表: 46 | 47 | ```mysql 48 | CREATE TABLE `t_user` ( 49 | `id` int(11) NOT NULL, 50 | `password` varchar(255) DEFAULT NULL, 51 | `username` varchar(255) DEFAULT NULL, 52 | PRIMARY KEY (`id`) 53 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 54 | ``` 55 | 56 | 一个整合了Spring Data JPA的SpringBoot工程,这里就不多说了。 57 | 58 | 59 | 60 | ## REQUIRED(默认的事务传播行为) 61 | 62 | 默认的事务传播行为是Propagation.REQUIRED,也就是说:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。 63 | 64 | ![](http://file.happyjava.cn/picgo/20190625212656.png) 65 | 66 | 下面,我们就验证下前面说的“不要循环嵌套事务方法”的问题: 67 | 68 | 现在有两个Service,如下: 69 | 70 | #### UserService.java 71 | 72 | ```java 73 | @Service 74 | public class UserService { 75 | 76 | @Autowired 77 | private UserRepo userRepo; 78 | 79 | @Transactional(propagation = Propagation.REQUIRED) 80 | public void insert() { 81 | UserEntity user = new UserEntity(); 82 | user.setUsername("happyjava"); 83 | user.setPassword("123456"); 84 | userRepo.save(user); 85 | } 86 | 87 | 88 | } 89 | ``` 90 | 91 | 这里很简单,就一个insert插入用户的方法。 92 | 93 | 94 | 95 | #### UserService2.java 96 | 97 | ```java 98 | @Service 99 | public class UserService2 { 100 | 101 | @Autowired 102 | private UserService userService; 103 | 104 | 105 | @Transactional 106 | public void inserBatch() { 107 | for (int i = 0; i < 10; i++) { 108 | if (i == 9) { 109 | throw new RuntimeException(); 110 | } 111 | userService.insert(); 112 | } 113 | } 114 | 115 | } 116 | ``` 117 | 118 | 注入UserService,循环十次调用参数方法。并且第十次抛出异常。调用inserBatch方法,查看结果: 119 | 120 | ```java 121 | @Test 122 | public void insertBatchTest() { 123 | userService2.inserBatch(); 124 | } 125 | ``` 126 | 127 | 结果如下: 128 | 129 | ![](http://file.happyjava.cn/picgo/20190625213402.png) 130 | 131 | 数据库中没有记录: 132 | 133 | ![](http://file.happyjava.cn/picgo/20190625213428.png) 134 | 135 | 这也证明了“如果当前存在事务,则加入该事务”的概念。如果以后还碰到有人说不要循环嵌套事务的话,可以叫他回去好好看看Spring的事务传播行为。 136 | 137 | 138 | 139 | ## SUPPORTS 140 | 141 | 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。也就是说,该模式是否支持事务,看调用它的方法是否有事务支持。测试代码如下: 142 | 143 | #### UserService 144 | 145 | ```java 146 | @Transactional(propagation = Propagation.SUPPORTS) 147 | public void insert() { 148 | UserEntity user = new UserEntity(); 149 | user.setUsername("happyjava"); 150 | user.setPassword("123456"); 151 | userRepo.save(user); 152 | throw new RuntimeException(); 153 | } 154 | ``` 155 | 156 | #### UserService2 157 | 158 | ```java 159 | public void insertWithoutTx() { 160 | userService.insert(); 161 | } 162 | ``` 163 | 164 | 调用的方法没有开启事务,运行结果: 165 | 166 | ![](http://file.happyjava.cn/picgo/20190625214428.png) 167 | 168 | ![](http://file.happyjava.cn/picgo/20190625214348.png) 169 | 170 | 运行报错了,但是数据却没有回滚掉。说明了insert方法是没有在事务中运行的。 171 | 172 | 173 | 174 | ## MANDATORY 175 | 176 | 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。mandatory中文是强制性的意思,表明了被修饰的方法,一定要在事务中去调用,否则会抛出异常。 177 | 178 | #### UserService.java 179 | 180 | ```java 181 | @Transactional(propagation = Propagation.MANDATORY) 182 | public void insert() { 183 | UserEntity user = new UserEntity(); 184 | user.setUsername("happyjava"); 185 | user.setPassword("123456"); 186 | userRepo.save(user); 187 | } 188 | ``` 189 | 190 | UserService2.java 191 | 192 | ```java 193 | public void insertWithoutTx() { 194 | userService.insert(); 195 | } 196 | ``` 197 | 198 | 调用: 199 | 200 | ```java 201 | @Test 202 | public void insertWithoutTxTest() { 203 | userService2.insertWithoutTx(); 204 | } 205 | ``` 206 | 207 | 运行结果: 208 | 209 | ![](http://file.happyjava.cn/picgo/20190625225611.png) 210 | 211 | 抛出了异常,提示没有存在的事务。 212 | 213 | 214 | 215 | ## REQUIRES_NEW 216 | 217 | 这个理解起来可能会比较绕,官方的解释是这样子的: 218 | 219 | ``` 220 | Create a new transaction, and suspend the current transaction if one exists. 221 | ``` 222 | 223 | 大意就是:重新创建一个新的事务,如果当前存在事务,延缓当前的事务。这个延缓,或者说挂起,可能理解起来比较难,下面通过例子来分析: 224 | 225 | #### UserService.java 226 | 227 | ```java 228 | @Transactional(propagation = Propagation.REQUIRES_NEW) 229 | public void insert() { 230 | UserEntity user = new UserEntity(); 231 | user.setUsername("happyjava"); 232 | user.setPassword("123456"); 233 | userRepo.save(user); 234 | } 235 | ``` 236 | 237 | 这个insert方法的传播行为改为REQUIRES_NEW。 238 | 239 | #### UserService2.java 240 | 241 | ```java 242 | @Transactional 243 | public void inserBatch() { 244 | UserEntity user = new UserEntity(); 245 | user.setUsername("初次调用"); 246 | user.setPassword("123456"); 247 | userRepo.save(user); 248 | for (int i = 0; i < 10; i++) { 249 | if (i == 9) { 250 | throw new RuntimeException(); 251 | } 252 | userService.insert(); 253 | } 254 | } 255 | ``` 256 | 257 | inserBatch拥有事务,然后后面循环调用的insert方法也有自己的事务。根据定义,inserBatch的事务会被延缓。具体表现就是:后面的10次循环的事务在每次循环结束之后都会提交自己的事务,而inserBatch的事务,要等循环方法走完之后再提交。但由于第10次循环会抛出异常,则inserBatch的事务会回滚,既数据库中不会存在:“初次调用”的记录: 258 | 259 | 测试代码: 260 | 261 | ```java 262 | @Test 263 | public void insertBatchTest() { 264 | userService2.inserBatch(); 265 | } 266 | ``` 267 | 268 | 执行结果: 269 | 270 | ![](http://file.happyjava.cn/picgo/20190625230827.png) 271 | 272 | ![](http://file.happyjava.cn/picgo/20190625230842.png) 273 | 274 | 这种情况,符合开始说的“不要循环嵌套事务方法”的说话,当然是否需要循环嵌套,还是要看业务逻辑的。 275 | 276 | 277 | 278 | ## NOT_SUPPORTED 279 | 280 | ``` 281 | Execute non-transactionally, suspend the current transaction if one exists. 282 | ``` 283 | 284 | 以非事务的方式运行,如果当前存在事务,暂停当前的事务。这种方式与REQUIRES_NEW有所类似,但是NOT_SUPPORTED修饰的方法其本身是没有事务的。这里就不做代码演示了。 285 | 286 | 287 | 288 | ## NEVER 289 | 290 | 以非事务的方式运行,如果当前存在事务,则抛出异常。 291 | 292 | ```java 293 | @Transactional(propagation = Propagation.NEVER) 294 | public void insert() { 295 | UserEntity user = new UserEntity(); 296 | user.setUsername("happyjava"); 297 | user.setPassword("123456"); 298 | userRepo.save(user); 299 | } 300 | ``` 301 | 302 | ```java 303 | @Transactional 304 | public void insertWithTx() { 305 | userService.insert(); 306 | } 307 | ``` 308 | 309 | 执行结果: 310 | 311 | ![](http://file.happyjava.cn/picgo/20190625231753.png) 312 | 313 | 314 | 315 | ## NESTED 316 | 317 | 如果没有事务,就新建一个事务;如果有,就在当前事务中嵌套其他事务。 318 | 319 | 这个也是理解起来比较费劲的一个行为。我们一步一步分析。 320 | 321 | 外围方法没有事务:这种情况跟REQUIRED是一样的,会新建一个事务。 322 | 323 | 外围方法如果存在事务:这种情况就会嵌套事务。所谓嵌套事务,大意就是,外围事务回滚,内嵌事务一定回滚,而内嵌事务可以单独回滚而不影响外围主事务和其他子事务。 324 | 325 | 由于本人使用Spring Data JPA 进行的演示代码,使用嵌套事务会提示: 326 | 327 | ``` 328 | org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities 329 | ``` 330 | 331 | 搜索了下,hibernate似乎不支持这种事务传播方式。所以这里就不做演示了 332 | 333 | 334 | 335 | ## 总结 336 | 337 | 事务传播行为,在开发中可能不会特别的留意到它(更多时候,我们可能只是使用默认的方式),但是还是需要对其要有所理解。希望本篇文章能让大家明白Spring的7种事务传播行为。 338 | 339 | 340 | 341 | 342 | 343 | -------------------------------------------------------------------------------- /transaction-propagation/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | cn.happyjava 12 | transaction-propagation 13 | 0.0.1-SNAPSHOT 14 | transaction-propagation 15 | Spring事务传播行为 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-jpa 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-devtools 30 | runtime 31 | true 32 | 33 | 34 | mysql 35 | mysql-connector-java 36 | runtime 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | true 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | 57 | 58 | 59 | 60 | 61 | aliyun 62 | https://maven.aliyun.com/repository/public 63 | 64 | true 65 | 66 | 67 | false 68 | 69 | 70 | 71 | 72 | 73 | aliyun-plugin 74 | https://maven.aliyun.com/repository/public 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /transaction-propagation/src/main/java/cn/happyjava/transactionpropagation/TransactionPropagationApplication.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.transactionpropagation; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class TransactionPropagationApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(TransactionPropagationApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /transaction-propagation/src/main/java/cn/happyjava/transactionpropagation/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.transactionpropagation.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * @author happy 9 | */ 10 | @Data 11 | @Entity 12 | @Table(name = "t_user") 13 | public class UserEntity { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.SEQUENCE) 17 | private Integer id; 18 | 19 | private String username; 20 | 21 | private String password; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /transaction-propagation/src/main/java/cn/happyjava/transactionpropagation/repo/UserRepo.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.transactionpropagation.repo; 2 | 3 | import cn.happyjava.transactionpropagation.entity.UserEntity; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | /** 7 | * @author happy 8 | */ 9 | public interface UserRepo extends CrudRepository { 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /transaction-propagation/src/main/java/cn/happyjava/transactionpropagation/service/UserService.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.transactionpropagation.service; 2 | 3 | import cn.happyjava.transactionpropagation.entity.UserEntity; 4 | import cn.happyjava.transactionpropagation.repo.UserRepo; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Propagation; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | @Service 11 | public class UserService { 12 | 13 | @Autowired 14 | private UserRepo userRepo; 15 | 16 | @Transactional(propagation = Propagation.NESTED) 17 | public void insert() { 18 | UserEntity user = new UserEntity(); 19 | user.setUsername("happyjava"); 20 | user.setPassword("123456"); 21 | userRepo.save(user); 22 | throw new RuntimeException(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /transaction-propagation/src/main/java/cn/happyjava/transactionpropagation/service/UserService2.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.transactionpropagation.service; 2 | 3 | import cn.happyjava.transactionpropagation.entity.UserEntity; 4 | import cn.happyjava.transactionpropagation.repo.UserRepo; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | @Service 10 | public class UserService2 { 11 | 12 | @Autowired 13 | private UserService userService; 14 | 15 | @Autowired 16 | UserRepo userRepo; 17 | 18 | @Transactional 19 | public void inserBatch() { 20 | UserEntity user = new UserEntity(); 21 | user.setUsername("初次调用"); 22 | user.setPassword("123456"); 23 | userRepo.save(user); 24 | for (int i = 0; i < 10; i++) { 25 | if (i == 9) { 26 | throw new RuntimeException(); 27 | } 28 | userService.insert(); 29 | } 30 | } 31 | 32 | public void insertWithoutTx() { 33 | userService.insert(); 34 | } 35 | 36 | @Transactional 37 | public void insertWithTx() { 38 | userService.insert(); 39 | } 40 | 41 | @Transactional 42 | public void insertMain() { 43 | UserEntity user = new UserEntity(); 44 | user.setUsername("主事务"); 45 | user.setPassword("apsdfk"); 46 | userRepo.save(user); 47 | 48 | // 内嵌事务 49 | try { 50 | userService.insert(); 51 | } catch (Exception e) { 52 | System.out.println("内嵌事务回滚"); 53 | } 54 | 55 | throw new RuntimeException(); 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /transaction-propagation/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai 2 | spring.datasource.username=root 3 | spring.datasource.password=123456 4 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 5 | spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 6 | spring.jpa.hibernate.ddl-auto=update 7 | spring.jpa.show-sql=true -------------------------------------------------------------------------------- /transaction-propagation/src/test/java/cn/happyjava/transactionpropagation/TransactionPropagationApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.happyjava.transactionpropagation; 2 | 3 | import cn.happyjava.transactionpropagation.entity.UserEntity; 4 | import cn.happyjava.transactionpropagation.repo.UserRepo; 5 | import cn.happyjava.transactionpropagation.service.UserService; 6 | import cn.happyjava.transactionpropagation.service.UserService2; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | public class TransactionPropagationApplicationTests { 16 | 17 | @Autowired 18 | UserService2 userService2; 19 | 20 | @Autowired 21 | UserService userService; 22 | 23 | @Autowired 24 | UserRepo userRepo; 25 | 26 | @Test 27 | public void insertBatchTest() { 28 | userService2.inserBatch(); 29 | } 30 | 31 | @Test 32 | public void insertWithoutTxTest() { 33 | userService2.insertWithoutTx(); 34 | } 35 | 36 | @Test 37 | public void insertWithTxTest() { 38 | userService2.insertWithTx(); 39 | } 40 | 41 | @Test 42 | public void insertMainTest() { 43 | userService2.insertMain(); 44 | } 45 | 46 | } 47 | --------------------------------------------------------------------------------