SpringBoot学习笔记
约定大于配置
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
2、通过IOC,依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;
M:数据和业务
C:交换
V:html
Spring Boot的主要优点:
为所有Spring开发者更快的入门
开箱即用 ,提供各种默认配置来简化项目配置
内嵌式容器简化Web项目
没有冗余代码生成和XML配置的要求
一.第一个springboot程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.codelorin.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class HelloController { @GetMapping("/") public String hello () { return "Hello,SpringBoot!!!" ; } }
二.自动装配原理
父项目管理所有的依赖版本,是SpringBoot的版本控制中心
1 2 3 4 5 6 7 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 2.2.5.RELEASE</version > <relativePath > ../../spring-boot-dependencies</relativePath > </parent >
启动器spring-boot-starter
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
springboot-boot-starter-xxx :就是spring-boot的场景启动器
spring-boot-starter-web :帮我们导入了web模块正常运行所依赖的组件;
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 star
默认的启动器
1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication public class SpringbootApplication { public static void main (String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
我们继续进去这个注解查看
里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
我们回到 SpringBootApplication 注解中继续看。
@EnableAutoConfiguration :开启自动配置功能**
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
三.yaml语法初解
更加优秀的文件格式
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 person: name: codelorin age: 3 happy: false birth: 2020 /1/1 maps: { k1: v1 ,k2: v2 } lists: - code - girl - musi dog: name: 五百 age: 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.codelorin.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;import java.util.Date;import java.util.List;import java.util.Map;@Data @AllArgsConstructor @NoArgsConstructor @Component @ConfigurationProperties(prefix = "person") public class Person { private String name; private Integer age; private Boolean happy; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog; }
@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
pringboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;
1 2 3 4 5 6 7 8 @Component @ConfigurationProperties(prefix = "person") @Validated public class Person { @Email(message="邮箱格式错误") private String name; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @NotNull(message="名字不能为空") private String userName;@Max(value=120,message="年龄最大不能查过120") private int age;@Email(message="邮箱格式错误") private String email;空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null , 无法查检长度为0 的字符串@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0 ,只对字符串,且会去掉前后空格.@NotEmpty 检查约束元素是否为NULL或者是EMPTY. Booelan检查 @AssertTrue 验证 Boolean 对象是否为 true @AssertFalse 验证 Boolean 对象是否为 false 长度检查 @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 @Length(min=, max=) string is between min and max included.日期检查 @Past 验证 Date 和 Calendar 对象是否在当前时间之前 @Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则.......等等 除此以外,我们还可以自定义一些数据校验规则
复杂类型封装,yml中可以封装对象 , 使用value就不支持
四.多环境配置文件 application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件 ;
我们需要通过一个配置来选择需要激活的环境:
1 spring.profiles.active =dev
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server: port: 8081 spring: profiles: active: prod --- server: port: 8083 spring: profiles: dev --- server: port: 8084 spring: profiles: prod
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
手动制定
1 java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
五.web开发
静态资源解决
静态资源
webjars /webjars
public static resources
优先级
resource>static(默认)>public
首页和图标
只要把主页放在静态资源目录下就可以了
5.1模板引擎 Thymeleaf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > </title > </head > <body > <h1 > 测试页面</h1 > <div th:text ="${msg}" > </div > <div th:utext ="${msg}" > </div > <h4 th:each ="user :${users}" th:text ="${user}" > </h4 > <h4 > <span th:each ="user:${users}" > [[${user}]]</span > </h4 >
5.2.整合数据库 JDBC JdbcTemplate主要提供以下几类方法:
execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
query方法及queryForXXX方法:用于执行查询相关语句;
call方法:用于执行存储过程、函数相关语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.codelorin.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import java.util.List;import java.util.Map;@RestController public class JdbcController { @Autowired JdbcTemplate jdbcTemplate; @GetMapping("/list") public List<Map<String, Object>> userList() { String sql = "select * from user " ; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } @GetMapping("/add") public String addUser () { String sql = "insert into user(name,pwd) values ('codelorin','123123')" ; jdbcTemplate.update(sql); return "addOk" ; } @GetMapping("/update/{id}") public String updateUser (@PathVariable("id") int id) { String sql = "update user set name=?,pwd=? where id=" + id; Object[] objects = new Object[2 ]; objects[0 ] = "codelorin" ; objects[1 ] = "123123" ; jdbcTemplate.update(sql, objects); return "updateOk" ; } @GetMapping("/delete/{id}") public String delUser (@PathVariable("id") int id) { String sql = "delete from employee where id=?" ; jdbcTemplate.update(sql, id); return "deleteOk" ; } }
整合数据源Druid 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package com.codelorin.config;import com.alibaba.druid.pool.DruidDataSource;import com.alibaba.druid.support.http.StatViewServlet;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;import java.util.HashMap;import java.util.Map;@Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource () { return new DruidDataSource(); } @Bean public ServletRegistrationBean statViewServlet () { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*" ); Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername" , "admin" ); initParams.put("loginPassword" , "123456" ); initParams.put("allow" , "" ); bean.setInitParameters(initParams); return bean; } @Bean public FilterRegistrationBean webStatFilter () { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions" , "*.js,*.css,/druid/*,/jdbc/*" ); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList("/*" )); return bean; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.21</version > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.17</version > </dependency >
Mybatis整合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.codelorin.mapper.UserMapper" > <select id ="queryUserList" resultType ="User" > select * from user </select > <select id ="queryUserById" resultType ="User" > select * from user where id=#{id} </select > <insert id ="addUser" parameterType ="User" > insert into user(id, name, pwd) values (#{id}, #{name}, #{pwd}) </insert > <update id ="updateUser" parameterType ="User" > update user set name =#{name}, pwd = #{pwd}, where id = #{id} </update > <delete id ="deleteUser" parameterType ="int" > delete from user where id = #{id} </delete > </mapper >
1 2 3 4 5 6 7 8 spring.datasource.username =root spring.datasource.password =123456 spring.datasource.url =jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver mybatis.type-aliases-package =com.codelorin.pojo mybatis.mapper-locations =classpath:mybatis/mapper/*.xml
六.Spring Security安全框架
认证 授权
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
WebSecurityConfigurerAdapter:自定义Security策略
AuthenticationManagerBuilder:自定义认证策略
@EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.codelorin.config;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/" ).permitAll() .antMatchers("/level1/**" ).hasRole("vip1" ) .antMatchers("/level2/**" ).hasRole("vip2" ) .antMatchers("/level3/**" ).hasRole("vip3" ); http.formLogin() .usernameParameter("username" ) .passwordParameter("password" ) .loginPage("/toLogin" ) .loginProcessingUrl("/login" ); http.csrf().disable(); http.logout().logoutSuccessUrl("/" ); http.rememberMe().rememberMeParameter("remember" ); } @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("codelorin" ).password(new BCryptPasswordEncoder().encode("lorin" )).roles("vip2" , "vip3" ) .and() .withUser("root" ).password(new BCryptPasswordEncoder().encode("lorin" )).roles("vip1" , "vip2" , "vip3" ) .and() .withUser("guest" ).password(new BCryptPasswordEncoder().encode("lorin" )).roles("vip1" ); } }
七.Shiro安全框架
快速入门
Subject 用户
SecurityManager 管理用户
Realm 连接数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.ini.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.apache.shiro.lang.util.Factory;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main (String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini" ); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); session.setAttribute("someKey" , "aValue" ); String value = (String) session.getAttribute("someKey" ); if (value.equals("aValue" )) { log.info("Retrieved the correct value! [" + value + "]" ); } if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr" , "vespa" ); token.setRememberMe(true ); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!" ); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it." ); } catch (AuthenticationException ae) { } } log.info("User [" + currentUser.getPrincipal() + "] logged in successfully." ); if (currentUser.hasRole("schwartz" )) { log.info("May the Schwartz be with you!" ); } else { log.info("Hello, mere mortal." ); } if (currentUser.isPermitted("lightsaber:wield" )) { log.info("You may use a lightsaber ring. Use it wisely." ); } else { log.info("Sorry, lightsaber rings are for schwartz masters only." ); } if (currentUser.isPermitted("winnebago:drive:eagle5" )) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!" ); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!" ); } currentUser.logout(); System.exit(0 ); } }
springboot集成
认证过滤器: anon:无需认证即可访问,游客身份。 authc:必须认证(登录)才能访问。 authcBasic:需要通过 httpBasic 认证。 user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。
授权过滤器: perms:必须拥有对某个资源的访问权限(授权)才能访问。 role:必须拥有某个角色权限才能访问。 port:请求的端口必须为指定值才可以访问。 rest:请求必须是 RESTful,method 为 post、get、delete、put。 ssl:必须是安全的 URL 请求,协议为 HTTPS。
ShiroConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.codelorin.config;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class ShiroConfig { public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager); return bean; } @Bean("name=securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager (@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } @Bean public UserRealm userRealm () { return new UserRealm(); } }
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package com.codelorin.controller;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller public class MyController { @RequestMapping({"/", "index"}) public String index (Model model) { model.addAttribute("msg" , "HelloShiro" ); return "index" ; } @RequestMapping("/user/add") public String add () { return "user/add" ; } @RequestMapping("/user/delete") public String delete () { return "user/delete" ; } @RequestMapping("/toLogin") public String toLogin () { return "login" ; } @RequestMapping("/login") public String login (String name, String pwd, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(name, pwd); try { subject.login(token); return "index" ; } catch (UnknownAccountException e) { model.addAttribute("msg" , "用户名错误" ); return "login" ; } catch (IncorrectCredentialsException e) { model.addAttribute("msg" , "密码错误" ); return "login" ; } } @RequestMapping("/unauth") @ResponseBody public String unauth () { return "未授权没有访问权限" ; } }
ShiroConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.codelorin.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap;import java.util.Map;@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/add" , "perms[user:add]" ); filterMap.put("/user/delete" , "perms[user:delete]" ); bean.setFilterChainDefinitionMap(filterMap); bean.setLoginUrl("/toLogin" ); bean.setUnauthorizedUrl("/unauth" ); return bean; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager (@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } @Bean public UserRealm userRealm () { return new UserRealm(); } @Bean public ShiroDialect getShiroDialect () { return new ShiroDialect(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.codelorin.config;import com.codelorin.pojo.User;import com.codelorin.service.UserService;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.session.Session;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.subject.Subject;import org.springframework.beans.factory.annotation.Autowired;public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { System.out.println("执行了授权方法" ); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Subject subject = SecurityUtils.getSubject(); User currentUser = (User) subject.getPrincipal(); info.addStringPermission(currentUser.getPerms()); Session session = subject.getSession(); session.setAttribute("loginUser" , currentUser); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { System.out.println("执行了认证方法" ); UsernamePasswordToken userToken = (UsernamePasswordToken) token; User user = userService.queryUserByName(userToken.getUsername()); if (user == null ) { return null ; } return new SimpleAuthenticationInfo(user, user.getPwd(), "" ); } }
八.Swagger-Ui
了解Swagger的概念及作用
掌握在项目中集成Swagger自动生成API文档
前后端分离
前端 -> 前端控制层、视图层
后端 -> 后端控制层、服务层、数据访问层
前后端通过API进行交互
前后端相对独立且松耦合
产生的问题
前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发
解决方案
首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险
Swagger
号称世界上最流行的API框架
Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
直接运行,在线测试API
支持多种语言 (如:Java,PHP等)
官网:https://swagger.io/
1 2 3 4 5 <dependency > <groupId > com.github.xiaoymin</groupId > <artifactId > knife4j-spring-boot-starter</artifactId > <version > 3.0.3</version > </dependency > >
生产环境关闭swagger
1 2 3 4 5 knife4j: # 开启增强配置 enable: true # 生产环境屏蔽 production: true
1 2 3 4 5 6 7 any() none() withMethodAnnotation(final Class<? extends Annotation> annotation) withClassAnnotation(final Class<? extends Annotation> annotation) basePackage(final String basePackage)
过滤什么路径我们通过PathSelectors类的4个方法来进行判断
PathSelectors.any(): 所有的api都显示
PathSelectors.none(): 所有的路径都不显示
PathSelectors.regex(String pathRegex): 按照String的matches方法进行匹配。例如:PathSelectors.regex(“/user/*”)
PathSelectors.ant(String antPattern): 按照Spring的AntPathMatcher提供的match方法进行匹配 例如:PathSelectors.ant(“/user/**“)
AntPathMatcher.match(String pattern, String path) 可以做URLs匹配,规则如下
?匹配一个字符
*匹配0个或多个字符
** 匹配0个或多个目录
只要接口返回中存在实体类就会被扫描到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.codelorin.pojo;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor @ApiModel(value="user对象",description="用户对象user") public class User { @ApiModelProperty(value="用户名",name="name",example="codelorin") private String username; @ApiModelProperty(value="密码",name="password",example="123") private String password; }
常用注解
Swagger的所有注解定义在io.swagger.annotations包下
下面列一些经常用到的,未列举出来的可以另行查阅说明:
Swagger注解
简单说明
@Api(tags = “xxx模块说明”)
作用在模块类上
@ApiOperation(“xxx接口说明”)
作用在接口方法上
@ApiModel(“xxxPOJO说明”)
作用在模型类上:如VO、BO
@ApiModelProperty(value = “xxx属性说明”,hidden = true)
作用在类方法和属性上,hidden设置为true可以隐藏该属性
@ApiParam(“xxx参数说明”)
作用在参数、方法和字段上,类似@ApiModelProperty
我们也可以给请求的接口配置一些注释
1 2 3 4 5 6 @ApiOperation("接口") @PostMapping("/kuang") @ResponseBody public String kuang (@ApiParam("这个名字会被返回") String username) { return username; }
这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!
相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。
Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package com.codelorin.config;import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Profile;import org.springframework.core.env.Environment;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.oas.annotations.EnableOpenApi;import springfox.documentation.service.ApiInfo;import springfox.documentation.service.Contact;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import java.util.ArrayList;@EnableKnife4j @Configuration public class SwaggerConfig { @Bean(value = "apiV1") public Docket apiV1 () { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("v1" ) .select() .apis(RequestHandlerSelectors.basePackage("com.demo.controller.v1" )) .paths(PathSelectors.any()) .build(); } @Bean(value = "apiV2") public Docket apiV2 () { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("v2" ) .select() .apis(RequestHandlerSelectors.basePackage("com.demo.controller.v2" )) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo () { return new ApiInfo("Swagger3接口文档" , "如有疑问,请联系开发工程师CodeLorin" , "1.0" , "https://www.codelorin.cn" , new Contact("CodeLorin" , "https://www.codelorin.cn" , "codelorin@163.com" ), "Apache 2.0" , "http://www.apache.org/licenses/LICENSE-2.0" , new ArrayList()); } }
九.异步开启 在启动类开启异步注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.codelorin;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync;@EnableAsync @SpringBootApplication public class SpringbootTestApplication { public static void main (String[] args) { SpringApplication.run(SpringbootTestApplication.class, args); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.codelorin.service;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;@Service public class AsyncService { @Async public void hello () { try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中...." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.codelorin.conrtoller;import com.codelorin.service.AsyncService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class HelloController { @Autowired AsyncService asyncService; @RequestMapping("/") public String hello () { asyncService.hello(); return "ok" ; } }
十.邮件发送 1 2 3 4 5 6 7 spring.mail.host =smtp.163.com spring.mail.username =bylorin@163.com spring.mail.password =JZYFBIUAXSOTBJIM spring.mail.properties.mail.smtp.ssL.enable =true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.codelorin;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.mail.SimpleMailMessage;import org.springframework.mail.javamail.JavaMailSenderImpl;import org.springframework.mail.javamail.MimeMessageHelper;import javax.mail.MessagingException;import javax.mail.internet.MimeMessage;import java.io.File;@SpringBootTest class SpringbootTestApplicationTests { @Autowired JavaMailSenderImpl mailSender; @Test void contextLoads () { SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); simpleMailMessage.setSubject("你好啊!!" ); simpleMailMessage.setText("这是springboot的测试" ); simpleMailMessage.setTo("codelorin@163.com" ); simpleMailMessage.setFrom("bylorin@163.com" ); mailSender.send(simpleMailMessage); } @Test void Test2 () throws MessagingException { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true ); helper.setSubject("你好啊!!" ); helper.setText("<p style='color:red'>hello</p>" , true ); helper.addAttachment("1.png" , new File("C:\\Users\\lcary\\Desktop\\1.png" )); helper.setTo("codelorin@163.com" ); helper.setFrom("bylorin@163.com" ); mailSender.send(message); } }
十一.定时任务
@EnableScheduling //开始定时任务
@Scheduled //什么时候执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.codelorin;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.annotation.EnableScheduling;@EnableAsync @EnableScheduling @SpringBootApplication public class SpringbootTestApplication { public static void main (String[] args) { SpringApplication.run(SpringbootTestApplication.class, args); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.codelorin.service;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Service;@Service public class ScheduledService { @Scheduled(cron = "0 41 22 * * ?") public void hello () { System.out.println("你被执行了!!" ); } }
十二.springboot整合redis
jedis: 采用的直连,不安全的,使用jedis pool连接池
lettuce: 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据,更像Nio模式
1.导入依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
2.配置连接
1 2 spring.redis.host =127.0.0.1 spring.redis.port =6379
Lili8080.!
3.