Spring Boot基础笔记

第一个Spring Boot程序

创建一个SpringBoot项目

Spring Boot基础笔记_第1张图片
Spring Boot基础笔记_第2张图片

这里的包名默认会在后面自动拼接项目名,但这是没有必要的,这会让包结构中多了一个以项目名为名的子包,因此可以手动删除拼接的项目名.

Spring Boot基础笔记_第3张图片
Spring Boot基础笔记_第4张图片
在SpringBoot中不需要了SpringMVC的繁琐的配置,项目创建出来就可以运行,运行的入口是SpringbootApplication类中的main方法,这是SpringBoot自动生成的,以后的各层代码都需要在这个类的同级目录中创建才能被该类扫描到.

可以直接应用注解创建各种层,如下面的controller层

@RestController
public class MyController {

    @RequestMapping("hello")
    public String hello() {
        return "hello";
    }
}

代码是跟SpringMVC相同的,只是缺少了配置.

修改banner

只需要在resources目录下创建banner.txt文件,并将自己喜欢的banner复制进去即可.

像这样
Spring Boot基础笔记_第5张图片

这样,以后启动SpringBoot的banner就会是自己的了.

像这样
Spring Boot基础笔记_第6张图片

yaml

yaml的语法

server:
  port: 8082

# 普通键值对
name: fisher

# 对象
user:
  name: fisher
  age: 17

# 对象的行内写法
user2: {name: fisher, age: 17}

# 数组
hobite:
  - SpringBoot
  - Mybatis
  - SpringMVC

# 数组的行内写法
hobite2: [SpringBoot, Mybatis, SpringMVC]

SpringBoot官方推荐使用yaml文件作为配置文件,因为yaml更加轻巧

yaml可以表示普通键值对/对象/数组

yaml给类赋值

yaml的功能十分强大,甚至可以直接给实体类赋值

在yaml中写类的赋值语句,在类中通过@ConfigurationProperties(prefix = "")注解直接导入yaml中的类,就可以通过yaml对类复制,具体代码如下

实体类User.java

@Component //注册
@ConfigurationProperties(prefix = "user")//指定配置文件中的类
public class User {
    private String name;
    private int age;
    private boolean isMan;
    private List<String> hobby;
    private Map<String, String> map;
    private Dog dog;//这个Dog也是一个实体类.里面有name和age属性
}

配置文件application.yaml

user:
  name: Fisher
  age: 17
  isMan: true
  hobby: [Java, Java, Java]
  map: {kay1: hello, kay2: word}
  dog:
    name: Tom
    age: 3

yaml中类的属性与实体类中的属性一一对应就可以通过这种方式给类赋值.

yaml还支持松散绑定,也就是在给类赋值的时候,yaml写is-man与isMan是相同的

JSR303校验

JSR303校验是用来校验类属性赋值数据的合法性

常用校验如下

Bean Validation 中内置的 constraintSpring Boot基础笔记_第7张图片

Hibernate Validator 附加的 constraint
Spring Boot基础笔记_第8张图片
JSR303校验的使用

@Validated //数据校验
public class Dog {
    @Email
    private String name;
    private int age;
}

JRS校验的使用很简单,只需要在该类注解@Validated,并在字段中注解需要的校验即可,如@Email

这时,该属性赋值时就要遵守JRS校验,否则程序就会报错

yaml多环境配置

多环境配置

在yaml中可以配置多个环境,通过用---分割,通过默认环境的spring:profiles:active选择所用的配置

server:
  port: 8081

spring:
  profiles:
    active: dev #所选环境名
---
server:
  port: 8082
spring:
  profiles: dev #该环境名

---
server:
  port: 8083
spring:
  profiles: test

SpringBoot的静态资源与首页

Springboot访问静态资源有两种方式

  • webjars, 通过localhost:8080/webjars...访问
  • 存放在public, static,recoureces中,通过localhost:8080/...即可直接访问
    • Spring Boot基础笔记_第9张图片
    • 这三者的优先级是recoureces>static>public

SpringBoot首页存放在静态资源目录下的idnex.html,也就是在public/resources/static这三个目录下的任意一个.

thymeleaf模板引擎

导入依赖

<dependency>
    <groupId>org.thymeleafgroupId>
    <artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
    <groupId>org.thymeleaf.extrasgroupId>
    <artifactId>thymeleaf-extras-java8timeartifactId>
dependency>

thymeleaf初体验

thymeleaf的视图解析器路径为

public static final String DEFAULT_PREFIX = "classpath:/templates/";

public static final String DEFAULT_SUFFIX = ".html";

因此需要把资源文件放在/templates目录下

Controller层代码

@Controller
public class MyController {
    @RequestMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("text", "word");
        return "hello"; //跳转到classpath:/templates/hello.html中
    }
}

资源文件hello.html


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
hello
<div th:text="${text}">div>
body>
html>

使用thymeleaf需要在头文件中导入xmlns:th="http://www.thymeleaf.org

thymeleaf的几个表达式


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<div th:text="${text}">div>
<div th:utext="${text}">div>
<div th:each="user:${users}" th:text="${user}">div>
body>
html>

th:text 文字转义,比如,如果text的值为

word

那他会原样输出这个字符串

th:utext文字不转义,上面的字符串会输出成word标题

th:each="user:${users}"遍历,遍历的结果存放在user中

url:@{}

国际化提取信息:#{}

SpringBoot项目案例

首页问题

首页由于需要走thymeleaf模板而不能放在静态资源目录中,只能放在tmplates目录中,因此要在地址栏中访问到首页就需要自定义一个视图解析器,当使用url访问//index.html就跳转到index.html

自定义配置文件的类需要由@Configration注解,并且实现WebMvcConfigurer接口,代码如下

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //自定义视图解析器
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //访问'/'时跳转到index,这里index走了SpringBoot的视图解析器
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

国际化

  1. 配置文件

    配置文件结构如下
    Spring Boot基础笔记_第10张图片

    其中login.properties为默认语言,login_en_US.properties为英文配置,login_zh_CN.properties为中文配置

    在任意打开一个配置文件,下部会有Resource Bunlde选项卡,点击,即可进行可视化文件配置,如图
    Spring Boot基础笔记_第11张图片

  2. 在配置文件中配置国际化配置文件的位置

    application.properties

    # 国际化配置文件的位置
    spring.messages.basename=i18n.login
    
  3. 页面资源文件中引用国际化配置文件

    就是在需要国际化的文字部分使用#{国际化文件字段名},例如

    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
    

    注意,要使用#{}

  4. 自定义语言解析类,即实现LocaleResolver接口,该接口位于org.springframework.web.servlet包下

    MyLocaleResolver.java

    public class MyLocaleResolver implements LocaleResolver {
    
        //解析语言请求
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            //获取请求中的语言
            String lang = request.getParameter("lang");
            Locale locale = Locale.getDefault();//取一个默认的Locale,如果没有参数就用这个默认的
    
            // 有lang参数时进行国际化
            if (!StringUtils.isEmpty(lang)) {
                String[] split = lang.split("_");
                locale = new Locale(split[0], split[1]);
            }
    
            return locale;
        }
    
        @Override
        public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    
        }
    }
    
  5. 注册自定义的国际化类

    MyMvcConfig类中

    // 在容器中注册自定义国际化组件
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }
    

现在,只要请求中包含lang参数,解析类就会解析,并根据lang的参数内容显示不同的语言.

拦截器

  1. 拦截器类需要实现HandlerInterceptor接口

    LoginHandlerInterceptor类中

    public class LoginHandlerInterceptor implements HandlerInterceptor {
        //请求前拦截
        @Override
        public boolean preHandle(HttpServletRequest request,
                                 HttpServletResponse response, 
                                 Object handler) throws Exception {
            Object username = request.getSession().getAttribute("username");
    
            //没有登录
            if (username == null) {
                request.getRequestDispatcher("/index.html").forward(request, response);
                return false;
            }
            return true;
        }
    }
    
  2. 拦截器配置

    MyMvcConfig类中

    //配置自定义拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/", "/index.html", "/login", "/css/**", "/img/**", "/js/**");
    }
    

    addInterceptor: 添加拦截器

    addPathPatterns方法: 添加拦截的资源

    excludePathPatterns方法: 添加不拦截的资源

thymeleaf的组件化

  1. 产生一个模块

    th:fragment="frag_name"定义模块名,实例代码如下

    
    
    
  2. 使用模块

    th:replace='~{模块文件::模块名}',实例代码如下

    <div th:replace="~{commons/commons::sidebar(active='main.html')}">div>
    

    需要用~{}应用模板

    ()放了传递的参数,在调用模板时会传递给模板

时间格式问题

thymeleaf的时间输出格式

thymeleaf中的#dates.format(data, fromat)方法可以自定义输出的时间格式,实例代码如下

<td th:text="${#dates.format(emp.getBirth(), 'yyyy-MM-dd')}">td>

修改SpringBoot的时间输入格式

application.properties

# 修改时间日期格式
spring.mvc.date-format=yyyy-MM-dd

配置之后,在输入框输入的时间格式是定义的时间格式,SpringBoot才会识别并转换成时间类

SpringBoot默认的时间格式是yyyy/MM/dd

thymeleaf的RestFull风格传参

thymeleaf的参数不能直接通过${}的方式取值,

而直接拼接字符串传递RestFull风格的参数IDEA会划红线的,尽管实际运行时没有什么问题

官方推荐下面的方式传递参数

th:href="@{/updataEmp/{id} (id=${emp.getId()})}

错误页面

将错误页面放在templates/error目录下,以错误类型命名,当发生页面错误时会自动跳转到对应错误类型的页面
Spring Boot基础笔记_第12张图片

session的方法

(HttpSession session)
session.invalidate();//清空session

整合JDBC

配置数据源

application.yaml

# 配置数据源
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306?useSSL=false&useUnicode=TRUE&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

使用原生的JDBC

@SpringBootTest
class Springboot03DataApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        String sql = "select * from mybatis.user";
        
        Connection connection = dataSource.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();
        
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            String passwd = resultSet.getString("passwd");
            System.out.println(id + name + passwd);
        }
        connection.close();
    }

}

使用SpringBoot原生JDBC操作跟原生JDBC操作区别不大

只是SpringBoot内置了DataSource实例,使用它可以自动加载配置文件的数据源配置.

SpringBoot的Templeate模板

SpringBoot中内置了许多xxxTemplate,这些是已经配置好的模板Bean,需要是直接拿来使用,其中就包括JDBC的CRUD操作.

使用JDBCTemplate进行CRUD操作

@RestController
public class JDBCController {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @RequestMapping("/userList")
    public List<Map<String, Object>> userList() {
        String sql = "select * from mybatis.user";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }

    @RequestMapping("/addList")
    public String addList() {
        String sql = "insert into mybatis.user (id, name, passwd) values (?, ?, ?)";
        jdbcTemplate.update(sql, 100, "borzj", "890809");
        return "Success";
    }
}

Druid的集成

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控 而生的 DB 连接池。

  1. 导入依赖

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.22version>
dependency>
  1. application.yaml指定数据源并注入其属性

    # 配置数据源
    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://localhost:3306?useSSL=false&useUnicode=TRUE&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        # 选择数据源
        type: com.alibaba.druid.pool.DruidDataSource
    
        #Spring Boot 默认是不注入这些属性值的,需要自己绑定
        #druid 数据源专有配置
        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:监控统计、log4j:日志记录、wall:防御sql注入
        #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
        #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
  2. 配置Druid

    1. 注入数据源并设置属性值

      @Bean
      @ConfigurationProperties(prefix = "spring.datasource") //指定属性信息位置
      public DataSource druidDataSource() {
          return new DruidDataSource();
      }
      
    2. 设置后台监控

      @Bean
      //后台监控
      public ServletRegistrationBean statViewServlet() {
          ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
      
          // 设置初始化参数
          Map<String, String> initParameters = new HashMap<>();
          // 登录用户名
          initParameters.put("loginUsername", "admin");
          // 登录密码
          initParameters.put("loginPassword", "root");
          // 允许谁能访问,第二个参数传递允许访问的IP地址,""表示谁都可以访问
          initParameters.put("allow", "");
      
          bean.setInitParameters(initParameters);
          return bean;
      }
      
    3. 设置过滤器

      @Bean
      //过滤器
      public FilterRegistrationBean webStatFilter() {
          FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
          bean.setFilter(new WebStatFilter());
      
          Map<String, String> initParameters = new HashMap<>();
          //exclusions 设置不进行统计的资源
          initParameters.put("exclusions", "*.js,*.css,/druid/*");
      
          bean.setInitParameters(initParameters);
          return bean;
      }
      

整合Mybatis

  1. 导入依赖

    
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.1.2version>
    dependency>
    
  2. 写实体类和Mapper接口

    UserMapper接口

    @Repository
    @Mapper
    public interface UserMapper {
    
        List<User> getUsers();
    
        User getUserByID(@Param("id") int id);
    
        int addUser(User user);
    
        int deleteUser(@Param("id") int id);
    
        int updateUser(User user);
    }
    

    @Mapper会自动扫描Mapper的包并创建Mapper实现类

  3. 实现Mapper接口

    UserMApper.xml

    这个文件需要放在resource中,位置如下
    Spring Boot基础笔记_第13张图片
    实现的写法无需演示,在Mybatis笔记中有.

  4. 配置Mybatis

    application.yaml

    # 整合Mybatis
    mybatis:
      type-aliases-package: pero.fisher.pojo
      mapper-locations: classpath:mybatis/mapper/*.xml
    

    这一步就是配置Mybatis的类型别名和注册所有的Mapper.xml

  5. 调用实现类进行CRUD

    @RestController
    public class UserController {
        @Autowired
        private UserMapper userMapper;
    
        @RequestMapping("/userList")
        public List<User> userList() {
            List<User> users = userMapper.getUsers();
            return users;
        }
    
    }
    

Spring Security

SpringSecurity是面向AOP编程,使用它时直接编写配置类即可

导入依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>

授权与认证

SpringSecurity配置类SecurityConfig

@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();
    }

    // 认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("fisher").password(new BCryptPasswordEncoder().encode("fisher")).roles("vip1", "vip2")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("vip1", "vip2", "vip3");
    }
}

@EnableWebSecurity打开SpringSecurity

配置类需要继承WebSecurityConfigurerAdapter

配置授权时重写configure(HttpSecurity http)方法,授权就是控制每种角色的访问权限

配置认证时重写configure(AuthenticationManagerBuilder auth)方法,认证就是将设置某个用户的用户名/密码/该用户属于什么角色

SpringSecurity 5.0+内置了许多加密方法,这些方法都是以PasswordEncoding结尾的,并且在SpringBoot 2.2.+版本后Security管理的密码必须进行加密,否则访问页面时报找不到PasswordEncoder的错

注销

在配置类的configure(HttpSecurity http)方法中开启注销页面

// 关闭csrf功能
http.csrf().disable();
//注销页面
http.logout().logoutSuccessUrl("/");

这个注销页面的路径在/logout中,

当使用get请求这个页面时,SpringBoot默认开启的csrf会禁止访问,因为get请求不安全,所以要想通过get访问到注销页面,需要http.csrf().disable();关闭csrf

权限控制

导入依赖


<dependency>
    <groupId>org.thymeleaf.extrasgroupId>
    <artifactId>thymeleaf-extras-springsecurity4artifactId>
    <version>3.0.4.RELEASEversion>
dependency>

这个包让thymeleaf与SpringSecurity结合, 在thymeleaf中使用Security的属性和方法,利用这个可以惊醒权限控制,不同的用户有不同权限,显示不同内容,这个功能似乎只在SpringBoot2.0.9以下的版本支持

引入xmls

<html xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
html>

几个方法

  • sec:authorize="isAuthenticated() 判断用户是否登录
  • sec:authorize="hasRole(‘vip1’) 判断登录的用户是否属于某角色
  • sec:authentication=“name” 取登录用户的用户名

自定义登录界面

http.formLogin()

  • loginPage()方法指定登录页面
  • loginProcessingUrl()方法指定验证界面
  • usernameParameter()方法指定用户名元素名
  • passwordParameter()方法指定密码元素名

自定义登录界面有两种思路

  1. 指定登录界面与验证界面,自定义登录界面跳转到验证界面

    configure(HttpSecurity http)方法中

    http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
    

    登录界面

    <form action="/login" method="post">
        <input type="submit" />
    form>
    
  2. 指定登录界面,在自定义登录界面中跳转到登录界面

    configure(HttpSecurity http)方法中

    http.formLogin().loginPage("/toLogin");
    

    登录界面

    <form action="/toLogin" method="post">
        <input type="submit" />
    form>
    

SpringSecurity默认的用户名元素名是username,密码元素名是password,如果自定义登录界面的元素名不同,可以自定用户名和密码的元素名,如下

//登录页面
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password");

记住我

configure(HttpSecurity http)方法内

//记住我
http.rememberMe();

即可在登录页面中开启记住我功能

自定义登录界面时,可以使用rememberMeParameter()方法向SpringSecurity指定记住我的元素名,如下

//记住我
http.rememberMe().rememberMeParameter("remember");

Shiro

shiro的组成
Spring Boot基础笔记_第14张图片

Shiro的三大对象

  • Subject 用户
  • SecurityManager 管理
  • Realm 连接数据

Shiro内置方法

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated();
currentUser.getPrincipal();
currentUser.hasRole("roleName");
currentUser.isPermitted("user:root");
currentUser.logout();

SpringBoot整合shiro

依赖


<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-springartifactId>
    <version>1.5.2version>
dependency>

shiro配置类ShiroConfig

@Configuration
public class ShiroConfig {

    // ShiroFilterFactoryBean对象
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        return bean;
    }

    // SecurityManager对象
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联Realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    // Realm对象
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

这个类注册Shiro的三个对象

其中Realm对象是自己创建的,这个类中写授权和认证规则,下面写出自定义的Realm类UserRealm

UserRealm

//自定义Realm
public class UserRealm extends AuthorizingRealm {

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("====>执行授权");
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("====>执行认证");
        return null;
    }
}

自定义Realm类需要继承AuthorizingRealm

Shiro的过滤器

Shiro内置过滤器

  • anon: 无需认证即可访问
  • authc: 必须认证才可访问
  • user: 必须记住我才可以访问
  • perms: 拥有对某个资源的权限才可访问
  • roles: 拥有某个角色才可访问

下面是为shiro设置过滤器的方法

ShiroConfiggetShiroFilterFactoryBean方法中

// ShiroFilterFactoryBean对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    // 设置安全管理器
    bean.setSecurityManager(defaultWebSecurityManager);

    // 添加shiro过滤器
    Map<String, String> filterMap = new LinkedHashMap<>();
    filterMap.put("/user/*", "authc");// 第二个参数为内置过滤器类型
    bean.setFilterChainDefinitionMap(filterMap);

    //设置登录页面
    bean.setLoginUrl("/toLogin");

    return bean;
}

核心代码是setFilterChainDefinitionMap()方法设置过滤器

setLoginUrl()方法设置登录界面,当没有通过认证时就会跳转到登录界面

Shiro的帐号密码认证

  1. 获取页面输入的帐号密码,并封装登录数据,执行登录方法

    @RequestMapping("/login")
    public String login(String username, String password, Model model ) {
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //封装当前的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //执行登录方法
        try {
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) { //捕获用户名异常
            model.addAttribute("msg", "用户名错误");
            return "login";
        } catch (IncorrectCredentialsException e) { // 捕获密码异常
            model.addAttribute("msg", "密码错误");
            return "login";
        }
    
    }
    
  2. 在自定义Realm的认证方法中判断用户名和密码是否正确

    UserRealm类中

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("====>执行认证");
        // 假装从数据库中取的用户名和密码
        String username = "root";
        String password = "root";
    
        //获取token
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    
        // 判断用户名是否正常
        if (!token.getUsername().equals(username)) {
            return null;//返回null时 会自动抛出UnknownAccountException异常
        }
    
        //进行密码认证, 只需要返回一个SimpleAuthenticationInfo,由shiro进行密码认证,
        return new SimpleAuthenticationInfo("", password, "");
    }
    

Shiro的访问授权

在配置类ShiroConfiggetShiroFilterFactoryBean()方法中设置页面的访问权限未授权页面

// ShiroFilterFactoryBean对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    // 设置安全管理器
    bean.setSecurityManager(defaultWebSecurityManager);

    // 添加shiro过滤器
    Map<String, String> filterMap = new LinkedHashMap<>();
    filterMap.put("/user/add", "perms[user:add]");//只有拥有user:add的用户才能访问,否则未授权
    filterMap.put("/user/*", "authc");// 第二个参数为内置过滤器类型

    bean.setFilterChainDefinitionMap(filterMap);

    //设置登录页面
    bean.setLoginUrl("/toLogin");
    //设置未授权页面
    bean.setUnauthorizedUrl("/uoauth");

    return bean;
}

授权的核心就是设置FilterChainDefinitionMap时,使用perms过滤器设置某页面的访问权限,只有拥有该权限的用户才可以访问

设置未授权页面使用setUnauthorizedUrl()方法,当某用户访问了未经授权的页面时会跳转到该页面

用户授权

思路是先在数据库中标记不同用户的权限,在访问时直接查询数据库中用户的权限授权

授权是在自定义Realm类的doGetAuthorizationInfo(PrincipalCollection principalCollection)方法中完成,而能够查询到用户的方法是doGetAuthenticationInfo(AuthenticationToken authenticationToken),因此需要将用户实从后者传递给前者.代码如下

//自定义Realm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userService;
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("====>执行授权");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        Subject subject = SecurityUtils.getSubject();
        // 获取认证方法传来的principal
        User user = (User) subject.getPrincipal();

        // 添加该用户的Perm权限
        info.addStringPermission(user.getPerms());

        return info;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("====>执行认证");

        //获取token
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //请求数据库
        User user = userService.getUserByName(token.getUsername());
        if (user == null) {
            return null;
        }

        //进行密码认证, 只需要返回一个SimpleAuthenticationInfo,由shiro进行密码认证,
        // 第一个参数可以传递对象
        return new SimpleAuthenticationInfo(user, user.getPasswd(), "");
    }
}

认证方法也就是这个类的第二个方法中SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)类的第一个参数可以传递对象,然后在授权方法中通过subject.getPrincipal()方法就可以获取到传递过来的对象.

Shiro与Thymeleaf结合

  1. 依赖

    
    <dependency>
        <groupId>com.github.theborakompanionigroupId>
        <artifactId>thymeleaf-extras-shiroartifactId>
        <version>2.0.0version>
    dependency>
    
  2. 注册ShiroDialect

    在Shiro的配置类ShiroConfig

    // 整合ShiroDialect,也就是Shiro与Thymeleaf的整合
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }
    
  3. 现在,就可以在前端代码中使用Shiro的方法了

    Shiro的头是这个xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"

    <body>
    <h1>首页h1>
    <p th:text="${msg}">p>
    <hr/>
    
       
    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">添加a>
    div>
    <a th:href="@{/user/update}">更新a>
    
    body>
    

    hasPermission方法判断用户是否有某种权限

关于Shiro的sessino

Shiro还有自己的session可以通过下面的方法设置session的值

Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser", user);

然后在前端可以取出来

<div th:if="session.loginUser != null">
    <p>已登录p>
div>

Swagger

Swagger号称世界上最流行的API框架

Swagger初体验

  1. 依赖

    
    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger-uiartifactId>
        <version>2.9.2version>
    dependency>
    
    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger2artifactId>
        <version>2.9.2version>
    dependency>
    
  2. 编写配置类

    @Configuration
    @EnableSwagger2 //开启swagger
    public class SwaggerConfig {
    }
    
  3. 访问swaggerlocalhost:8080/swagger-ui.htmlSpring Boot基础笔记_第15张图片

自定义Swagger信息

在配置类中配置一个Docket实例

@Configuration
@EnableSwagger2 //开启swagger
public class SwaggerConfig {

    //配置swagger的Docket实例
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        //作者信息
        Contact contact = new Contact("Fisher", "https://borzj.github.io", "fisher4@gmail");

        return new ApiInfo("Fisher的Swagger",
                           "这是我的第一个Swagger",
                           "1.0",
                           "https://borzj.github.io",
                           contact,
                           "Apache 2.0",
                           "http://www.apache.org/licenses/LICENSE-2.0",
                           new ArrayList<VendorExtension>());
    }
}

通过Docket().apiInfo()自定义Swagger信息

设置Swagger扫描接口和开关

//配置swagger的Docket实例
    @Bean
    public Docket docket(Environment environment) {
        // 设置要判断的环境
        Profiles profiles = Profiles.of("dev");
        // 判断当前环境是不是设置的指定环境,如果是,返回true
        boolean flag = environment.acceptsProfiles(profiles);

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(flag) //控制Swagger启动
                .select().apis(RequestHandlerSelectors.basePackage("pero.fisher.controller")).build();// 设置要扫描的接口
    }

Docket.select().apis(RequestHandlerSelectors.basePackage("pero.fisher.controller")).build()设置扫描的接口,也就是访问什么时接收扫描

environment.acceptsProfiles(profiles)判断现在的环境是不是指定环境

Docket.enable()参数为true开启swagger,参数为false关闭

Swagger的分组

设置分组

Docket(DocumentationType.SWAGGER_2).groupName("Borjigin");

设置多个分组时就写多个返回Docket的方法,每个Docket添加到不同的分组

@Bean
public Docket docket1() {
    return new Docket(DocumentationType.SWAGGER_2).groupName("Borjigin");
}

Swagger接口注释

实体类中的注释

@ApiModel("用户实体类")

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;
}

@ApiModel("")@Api("")为实体类名注释

@ApiModelProperty("")为实体类属性名注释

下面让Swagger扫描到这个类就可以在Swagger中显示它了

让Swagger接管实体类

Controller类中

@PostMapping("/user")
@ResponseBody
// 接口中返回值为实体类就会被Swagger扫描到
public User user() {
    return new User();
}

在Controller类中,只要方法的返回值是实体类,并且设置一个访问路径让他可以访问,那这个返回的实体类就可以被Swagger扫描到

在Swagger界面中是这样显示的
Spring Boot基础笔记_第16张图片

方法和参数的注释

@ApiOperation("hello控制类,显示hello")
@GetMapping("/hello")
@ResponseBody
public String hello(@ApiParam("用户名") String username) {
    return "hello";
}

@ApiOperation("")给一个方法注释

@ApiParam("")给一个参数注释

在Swagger中是这样显示的
Spring Boot基础笔记_第17张图片

SpringBoot异步任务

有时候某个方法运行的十分漫长,为了不影响页面显示,提升用户体验,会将这种方法创建一个线程独立运行

  1. 在方法中标注这是一个异步方法

    @Service
    public class AsyncService {
    
        // 注解这是一个异步方法
        @Async
        public void hello() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("传输成功");
    
        }
    }
    

    @Async注解会向SpringBoot声明这是一个异步方法,SpringBoot会为这个方法创建一个线程

  2. 开启异步功能

    Application类中

    // 开启异步功能
    @EnableAsync
    @SpringBootApplication
    public class Springboot07TaskApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot07TaskApplication.class, args);
        }
    
    }
    

    @EnableAsync注解开启异步功能

现在,调用这个方法就会异步运行.

SpringBoot邮件任务

依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-mailartifactId>
dependency>

配置文件application.yaml

spring:
  mail:
    username: [email protected]
    password: tkxbimgbzkrehecf
    host: smtp.qq.com
    # 开启加密验证
    properties.mail.smtp.ssl.enbale: true

加密验证只有qq邮箱需要开启

简单的邮件发送

@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads() {
    SimpleMailMessage message = new SimpleMailMessage();

    //设置邮件主题
    message.setSubject("hello,我在用SpringBoot给你发邮件");
    //设置文本内容
    message.setText("我正在学习SpringBoot的邮件任务,很简单哦");

    //设置收信人
    message.setTo("[email protected]");
    //设置发信人
    message.setFrom("[email protected]");

    //发送邮件
    mailSender.send(message);
}

复杂的邮件发送

multipart:是否支持多文件

@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads2() throws MessagingException {
    // 创建一个复杂邮件
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    //利用Helper进行文件组装
    //multipart:是否支持多文件
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

    helper.setSubject("这是SpringBoot发送的一个复杂文件");
    // html:是否解析html文本
    helper.setText("

hello 我是红色的文本

"
, true); //发送附件 helper.addAttachment("1.jpg", new File("/home/fisher/pictures/head.jpg")); helper.addAttachment("2.jpg", new File("/home/fisher/pictures/head.jpg")); helper.setTo("[email protected]"); helper.setFrom("[email protected]"); mailSender.send(mimeMessage); }

SpringBoot的定时执行任务功能

定时任务就是在指定时间执行的任务,SpringBoot中使用两个注解就可以进行创建定时任务

@EnableScheduling开启执行任务功能,与开启异步任务的注解相同,该注解也是放在Application类上的

@Scheduled(cron)设置执行的时间

@Service
public class ScheduledService {
    @Scheduled(cron = "0/1 * * * * ?")
    public void hello() {
        System.out.println("hello");
    }
}

@Scheduled注解的方法是可以被SpringBoot扫描到的方法才可以,因为这种方法需要执行多次,而不是执行一次就结束了

cron表达式
Spring Boot基础笔记_第18张图片
表达式示例

(1)0 0 2 1 * ? * 表示在每月的1日的凌晨2点调整任务

(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业

(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作

(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点

(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

(6)0 0 12 ? * WED 表示每个星期三中午12点

(7)0 0 12 * * ? 每天中午12点触发

(8)0 15 10 ? * * 每天上午10:15触发

(9)0 15 10 * * ? 每天上午10:15触发

(10)0 15 10 * * ? * 每天上午10:15触发

(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发

(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发

(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发

(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发

(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发

(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发

(18)0 15 10 15 * ? 每月15日上午10:15触发

(19)0 15 10 L * ? 每月最后一日的上午10:15触发

(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发

(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发

(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

Dubbo与ZooKeeper

Dubbo

Dubbo是一款高性能 轻量级的开源Java RPC(远程方法调度)框架,提供了三大核心功能: 面向接口的远程方法调用/容错和负载均衡,以及服务自动注册和发现.
Spring Boot基础笔记_第19张图片

ZooKeeper

ZooKeeper是一个分布式的开源的分布式应用程序协调服务,目标是封装好复杂易出错的关键服务,提供服务注册与发布功能

Linux下ZooKeeper的下载安装与启动

  1. ZooKeeper的下载: ZooKeeper下载位置

  2. 下载完是一个压缩文件,解压缩放在自己喜欢的目录中

  3. 将conf目录下的zoo_sample.cfg复制一份,并改名为zoo.cfg像这样
    Spring Boot基础笔记_第20张图片

  4. 在ZooKeeper的根目录新建一个目录存放data,这里将目录起名为data
    Spring Boot基础笔记_第21张图片

  5. 在刚才的配置文件zoo.cfg中修改dataDir为刚才创建的目录data

Spring Boot基础笔记_第22张图片
6. ZooKeeper服务端的命令

  • 启动服务: ./zkServer.sh start
    在这里插入图片描述

  • 查看服务状态: ./zkServer.sh status

    • 服务开启时的状态
      在这里插入图片描述

    • 服务关闭时状态在这里插入图片描述

  • 关闭服务: ./zkServer.sh stop在这里插入图片描述

  1. Zookeeper客户端命令: 打开客户端: ./zkCli.sh
    Spring Boot基础笔记_第23张图片

Dubbo-admin下载与打包

Dubbo-admin是一个监控后台,可以监控ZooKeeper注册中心的哪些任务被注册,哪些任务被消费等

  1. 下载Dubbo-admin

    GitHub地址
    Spring Boot基础笔记_第24张图片

  2. 下载的是一个ZIP,这是一个SpringBoot项目,将其解压并进行Maven打包

    打包方式: 在要打包的根目录(也就是dubbo-admin-master目录)中,执行mvn clean package -Dmaven.test.skip=true

  3. 打包完成后,在dubbo-admin-master/dubbo-admin/目录下,会出现一个target目录,执行这个目录中的dubbo-admin-0.0.1-SNAPSHOT.jar包,就可开启Dubbo-admin,前提是ZooKeeper是开启的

    这个项目的默认端口是7001

    账户: root

    密码: rootSpring Boot基础笔记_第25张图片

远程服务注册与调用

注册

依赖


<dependency>
    <groupId>org.apache.dubbogroupId>
    <artifactId>dubbo-spring-boot-starterartifactId>
    <version>2.7.6version>
dependency>

<dependency>
    <groupId>com.github.sgroschupfgroupId>
    <artifactId>zkclientartifactId>
    <version>0.1version>
dependency>

<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-frameworkartifactId>
    <version>2.12.0version>
dependency>
<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-recipesartifactId>
    <version>2.12.0version>
dependency>
<dependency>
    <groupId>org.apache.zookeepergroupId>
    <artifactId>zookeeperartifactId>
    <version>3.6.0version>
    
    <exclusions>
        <exclusion>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-log4j12artifactId>
        exclusion>
    exclusions>
dependency>

提供者配置

server.port=8001

# 服务应用的名字
dubbo.application.name=private-server
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 需要被注册的服务
dubbo.scan.base-packages=pero.fisher.service

向ZooKeeper注册服务

import org.apache.dubbo.config.annotation.Service;

@Service
public class TicketServiceImpl implements TicketService {

    @Override
    public String getTicket() {
        return "获得一张票";
    }
}

@Service通过dubbo注册了这个接口

注意: @Service的包一定是dubbo的包

打开dubbo-admin可以开到注册状态
Spring Boot基础笔记_第26张图片

调用

依赖

调用的依赖和注册的依赖是相同的

消费者配置

server.port=8222

dubbo.application.name=my-consumer-server
dubbo.registry.address=zookeeper://127.0.0.1:2181
server.port=8222

dubbo.application.name=my-consumer-server
dubbo.registry.address=zookeeper://127.0.0.1:2181

消费者只需要配置名字和ZooKeeper的地址就可以,不需要注册服务

消费

import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Reference
    private TicketService ticketService;

    public void buyTicket() {
        System.out.println(ticketService.getTicket());
    }
}

使用@Reference注解取出注册中心的接口

这个接口是在消费者中没有的,因此消费者需要在创建与提供者相同的包和类

你可能感兴趣的:(Spring Boot基础笔记)