浅谈springboot

目录

1. springboot概述

2. 第一个springboot项目

2.1 创建项目并运行

2.2 修改banner

2.3 项目打包成package

3. 浅谈SpringBoot原理

3.1 @SpringBootApplication注解

3.2 SpringApplication.run 

 4. springboot项目练手笔记

5.整合jdbc

5.1 建立springboot项目的时候添加jdbc、MySQL和web依赖

5.2 编写配置文件application.yaml,配置datasource:

5.3 编写controller

6. 整合Druid数据源

6.1 Druid简介

6.2 整合步骤

6.2.1 在第五章节的基础上,添加druid和log4j的依赖

6.2.2 在第五章节的基础上,在properties.yaml文件中添加如下配置:

6.2.3 编写配置类,加载配置文件,初始化druid数据源

 7. 整合mybatis

7.1 添加依赖

7.2 编写实体类和mapper接口,编写mapper映射文件

7.3 编写application.properties文件

7.4 实现controller类

 7.5 浏览器输入请求,得到返回结果

7.6 一个奇怪的问题

8. SpringSecurity

8.1 简介

8.2 测试项目

8.2.1 添加依赖

8.2.2 实现控制类

8.2.3 实现安全配置类

8.3 项目原理讲解

9. shiro

9.1 简介

 9.2 shiro执行流程简介

10 swagger

10.1简介

10.2使用

10.2.1 配置swagger

 10.2.2 设置swagger

10.3 测试

11. 异步任务和定时任务

11.1 异步任务

11.2 定时任务

12 dubbo

12.1 dubbo概述

12.2 使用

12.2.1 provider-server

​ 12.2.2 consumer-server


写在前面:这篇文章是在看狂神说Java系列视频中关于springboot的笔记,文章里面也会用到一些狂神文章的资源【图片、运行原理流程等】。

1. springboot概述

2. 第一个springboot项目

2.1 创建项目并运行

创建一个springboot项目后,在Application主启动类的当前目录下创建文件夹,springboot项目只会扫描当前目录下的文件。

新建一个controller包,实现HelloController类,这个控制器会在用户从页面输入"hello"请求后向页面返回一个hello字符串

@Controller
public class HelloController {
    @GetMapping("hello")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

运行Application类,浏览器中输入请求,返回"hello"

2.2 修改banner

在springboot项目发布的时候,控制台会打印出springboot的banner

 想要修改banner,只需要resources文件夹下新建一个banner.txt,然后从网站站拷贝一个你想要的banner就好了。Spring Boot banner在线生成工具,制作下载banner.txt,修改替换banner.txt文字实现自定义,个性化启动banner-bootschool.net

2.3 项目打包成package

将项目打包成package后,它会变成一个jar包,你只需要在目录文件下打开控制台,输入 java -jar .jar包名称, 你的springboot项目就会启动了。不过这样的话需要手动关闭项目,不然项目会一直运行,端口号也会被占用。

3. 浅谈SpringBoot原理

3.1 @SpringBootApplication注解

SpringBoot项目主启动类上有一个注解@SpringBootApplication,它的主要结构如下

@SpringBootApplication

        @SpringBootConfiguration

                @Configuration //标志这是一个配置类

        @EnableAutoConfiguration

                @Import({AutoConfigurationImportSelector.class}) //自动获取注解选择器

@SpringBootApplication下有两个注解:@SpringBootConfiguration和@EnableAutoConfiguration。@SpringBootConfiguration注解下有一个@Configuration注解,也就是说它让主启动类变成了一个配置类【如果对@Configuration注解有问题的,先了解SpringMVC中@Configuration的用法】;@EnableAutoConfiguration注解下引入了一个AutoConfigurationImportSelector的类信息,从命名可以猜测它与自动配置选择器有关。进入这个类的源码,可以看到它有一个getCandidateConfigurations的方法,用来获取候选的配置

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

这个方法通过反射获取了所有的候选配置,并且如果候选配置为空的话,assert就断言出 在META-INF/spring.factories 中找不到自动配置类;如果候选配置不为空,那么返回这些候选配置。

META-INF/spring.factories存在与springboot项目的External Libraries文件夹中,也就是依赖文件中,在它的autoconfiguration包下:

这个文件中有很多的默认配置,每个配置项都对应了一个XXXAutoConfiguration类,和一个XXXProperties类。XXXProterties类里面主要是配置属性以及属性的get/set方法,XXXAutoConfiguration类中一定会用到@ConditionalOnXXX(args...)或者@ConditioanlOnMissingXXX(args...)这样的注解。在AutoConfigurationImportSelector类中,有个selectImports的方法:

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

 当存在注解元数据的时候,它才返回获得的注解;不存在注解元数据的时候,就返回NO_IMPORTS。这个方法就和@Conditional注解联系起来了。如果我们在pom.xml文件中引入依赖的话,那么ConditionalOn注解就满足条件,生效,isEnabled条件也就满足,获取配置;否则,条件不满足,相对应的类也就无法使用。这也是为什么spring.factories文件中已经配置了那么多的环境,我们还要引入依赖;只有引入了相应的依赖,@Conditional和isEnabled条件才会被满足。

3.2 SpringApplication.run 

SpringApplicatio主要做了以下四件事情:

1. 推断应用的类型是普通的项目还是Web项目

2. 查找并加载所有可用的初始化器,设置到initializers属性中

3. 找出所有的应用程序监听器,设置到listeners属性中

4. 推断并设置main方法的定义类,找到运行的主类

run方法流程

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASG9uZ2Jpbioq,size_20,color_FFFFFF,t_70,g_se,x_16

 4. springboot项目练手笔记

5.整合jdbc

5.1 建立springboot项目的时候添加jdbc、MySQL和web依赖

5.2 编写配置文件application.yaml,配置datasource:

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

想要知道datasource所有可配置属性,可查看DataSourceProperties类,部分属性如下:

 在springboot中,存在很多的XXXTemplate,这些Template都是springboot预先定义好的模板,拿来即用。模板的细节由用户编写配置文件来定义。在整合jdbc中,springboot使用了JdbcTemplate模板,使用datasource来创建一个JdbcTemplate。在JdbcTemplateConfiguration类中,有如下代码:

至此,我们可以分析:在springboot中使用jdbc,编写好配置文件配置datasource后,由DataSourceAutoConfiguration类生成一个DataSource,并被JdbcTemplate使用。使用JdbcTemplate直接就可以操作数据库了。

5.3 编写controller

@RestController
public class JDBCController {
    @Autowired
    JdbcTemplate jdbcTemplate;

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

    @RequestMapping("updateUser/{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] = "宏彬";
        objects[1] = "zzzzzz";
        jdbcTemplate.update(sql, objects);
        return "update-ok";
    }

    @RequestMapping("deleteUser/{id}")
    public String deleteUser(@PathVariable("id") int id){
        String sql = "delete from user where id=" + id;
        jdbcTemplate.update(sql);
        return "update-ok";
    }

    @RequestMapping("addUser")
    public String addUser(){
        String sql = "insert into user(id, name, pwd) VALUES (7, '图图', '987456')";
        jdbcTemplate.update(sql);
        return "update-ok";
    }
}

 可以很明显地发现,使用springboot确实简化了开发者的很多任务,这是实实在在的。不像ssm一样,只是把Java代码变成配置文件了。

6. 整合Druid数据源

6.1 Druid简介

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

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

6.2 整合步骤

6.2.1 在第五章节的基础上,添加druid和log4j的依赖

6.2.2 在第五章节的基础上,在properties.yaml文件中添加如下配置:

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=500P

6.2.3 编写配置类,加载配置文件,初始化druid数据源

//通过@Configuration注解类将配置文件加载进行,初始化druid。
//因为springboot没有.xml文件,所以只能使用配置类
@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean bean = new                      // druid下的所有請求都會被解析到登錄頁面
                ServletRegistrationBean(new StatViewServlet(), "/druid/*");

//        设置属性,如登录名、密码、允许登录拦截等,具体属性可以在 StatViewSerlvet的父类ResourceServlet中查看
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "123456");
        initParams.put("allow", "");

        bean.setInitParameters(initParams);
        return bean;
    }

//    配置web和druid数据源之间的管理关联监控统计
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        Map<String, String> initParams = new HashMap<>();
//        exclusions: 设置对哪些请求进行过滤排除掉,不统计
//        所有可设置属性在WebStatFilter类中进行查看
        initParams.put("exclusions", "*.js, *.css, /druid/*, /jdbc/*");
        bean.setInitParameters(initParams);

//        过滤所有请求
        bean.setUrlPatterns(Arrays.asList("/*"));

        return bean;
    }

}

至此,springboot整合druid已经完成了,在浏览器中输入localhost:8080/druid便会跳转到druid后台监控页面,在这个页面你能查看很多监控记录。

 7. 整合mybatis

7.1 添加依赖

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

7.2 编写实体类和mapper接口,编写mapper映射文件

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}
@Mapper
@Repository
public interface UserMapper {

    List<User> queryUserList();

    User getUserById(int id);

    int addUser(User user);

    int deleteUserById(int id);

    int updateUser(User user);
}
<?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.example.springbootmybatis.mapper.UserMapper">

    <select id="queryUserList" resultType="User">
        select * from user
    </select>


</mapper>

7.3 编写application.properties文件

spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.type-aliases-package=com.example.springbootmybatis.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

7.4 实现controller类

@RestController
public class UserController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping("queryUserList")
    public List<User> queryUserList(){
        List<User> users = userMapper.queryUserList();
        return users;
    }

}

 7.5 浏览器输入请求,得到返回结果

7.6 一个奇怪的问题

在配置application文件的时候,可以使用.properties文件和.yaml文件。这里我使用了.properties文件,因为刚开始使用的是.yaml文件,结果出现了很奇怪的错误:找不到userMapper.xml文件。propertise.yaml文件如下

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  type-aliases-package: com.example.srpingbootmybatis.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

具体情况如下

  • 1.  当我userMapper.xml文件位于resources/mybatis/userMapper.xml,而关于userMapper.xml文件的位置声明如上的时候,项目启动时不报错,输入查询请求后报错(userMapper.xml文件应该在resources/mybatis/mapper/userMapper.xml,但是我不小心放错路径了):页面为error页面,后台报错找不到方法;如果对应的方法不调用userMapper.queryUserList()方法,而是输出userMapper,则不报错且正常输出
  • 2.  当我将userMapper.xml放于resources/mybatis/mapper/userMapper.xml处时,项目启动的时候就报错了

8. SpringSecurity

8.1 简介

springsecurity是一个功能强大且高强度可定制的身份验证和访问控制框架,它实际上是保护基于spring的应用程序的标准。

springsecurity是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有的spring项目一样,spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求。

一般来说,web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证是指验证某个用户是否为系统中的合法主体,即登录功能。用户授权指的是验证某个用户是否具有权限执行某个操作。不同用户所应具有的权限是不同的,一般来说,系统会为不同的用户分配不同的角色,每个角色对应一系列的权限。

8.2 测试项目

8.2.1 添加依赖

    <dependencies>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.15.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

8.2.2 实现控制类

@Controller
public class RouterController {
//    接收 访问首页 的请求
    @RequestMapping({"/", "/index"})
    public String index(){
        return "index";
    }

//    接收 登录 的请求
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "views/login";
    }

//    接收 访问不同页面level1 的请求
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
        return "views/level1/" + id;
    }

//    接收 访问不同页面level2 的请求
   @RequestMapping("/level2/{id}")
   public String level2(@PathVariable("id") int id){
        return "views/level2/"+id;
   }

//   接收 访问不同页面level3 的请求
   @RequestMapping("/level3/{id}")
   public String level3(@PathVariable("id") int id){
        return "views/level3/"+id;
   }

}

在这个控制类中,通过接收不同的请求来实现访问不同服务器资源的功能。

8.2.3 实现安全配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        定义授权规则:首页所有人都可以访问,功能页只有具有权限的人才能访问
        http.authorizeHttpRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasAnyRole("vip1")
                .antMatchers("/level2/**").hasAnyRole("vip2")
                .antMatchers("/level3/**").hasAnyRole("vip3");

//        没有登录的话到登录页面,需要主动开启
//        springboot会自动发出一个 /login 的请求,然后自动跳转到springboot内置的登录页面
        http.formLogin();

//      在springboot中,登录验证是由框架自定义的操作进行处理的,比如登录的页面、用户名和密码的接收
//        当然,用户也可以定制自己的登录页面
/****************************************************************************************************
 *      http.formLogin().loginPage("/toLogin") // 将需要登录时,发出的请求改为 toLogin                    *
 *              .usernameParameter("userName")  // 获取name属性为userName的input                       *
 *              .passwordParameter("pwd")      //  获取name属性为pwd的input                            *
 *              .loginProcessingUrl("/login"); // 当在自己定制的登录页面提交登录信息后,设置处理登录的请求      *
 *****************************************************************************************************/


//      注销功能,会发出一个 /logout 请求,成功注销的话重定向到 /login?logout登录页面,并显示
//        已注销的提示
//        此时回到未登录状态
        http.logout();
        http.csrf().disable(); //关闭csrf功能,登陆失败的可能原因
//        http.logout().logoutSuccessUrl("/"); 注销成功后,跳到首页

//        记住我 功能, cookie, 默认保存两周
        http.rememberMe();

//        当使用了自己定制的登陆页面时,想要使用 记住我 功能
//        http.rememberMe().rememberMeParameter("remember"); 一个name为remember的checkbox

//        如果想看更多设置,可下载源码后查看注释,注释写得很详细
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        设置不同的登录用户名、密码、授权,
//        也可以通过连数据
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("plane").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
                .and()
                .withUser("红猪").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2")
                .and()
                .withUser("宏彬").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1" ,"vip3");
    }
}

8.3 项目原理讲解

上面的几个小节我们就完成了测试项目的搭建,这一小节进行项目讲解。

通过浏览器一开始访问项目的时候,controller将页面跳转到index首页,此时还未登录;点击页面链接访问其它资源,此时securityconfig类检查权限不够,就自动发出一个/login的请求,跳转到springboot内置的一个登录页面;登录成功后,回到首页,根据权限可进行操作。注销功能会就收一个/logout请求并进行处理,跳转到一个springboot内置的确认注销页面,注销后回到登录页面并提示已注销。

springsecurity还允许用户自定义登录页面和注销页面等,具体实现看上面代码的注释部分。

9. shiro

9.1 简介

Apache Shiro是一个Java的安全(权限)框架,它可以非常容易地开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以完成认证,授权,加密,会话管理,Web集成,缓存等。

几个重要的方法:

1. Subject currentUser = SecurityUtils.getSubject();   //获得当前登录用户

2. Session session = currentUser.getSession();   //获得用户的对话

3. currentUser.isAuthenticated();   当前用户是否验证

4. currentUser.getPrincipal();   获得当前用户验证

5. currentUser.hasRole("schwartz");   当前用户是否具有某个角色

6. currentUser。isPermitted(“lightsaber:wield")  当前用户是否具有某个权限

7. current.logout(); 当前用户注销

 9.2 shiro执行流程简介

在shiro中,有三个重要的对象:Subject, SecurityManager和Realm。Subject代表用户;SecurityManager代表安全管理,所有具体的交互都通过它来进行控制;Realm提供认证和授权的数据【即登录和权限验证】。

实现Realm需要继承AuthorizingReaml类,并重写它的授权和验证方法:

public class UserReaml extends AuthorizingRealm {
    @Autowired
    UserMapper userMapper;

//    授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("进入了授权方法doGetAuthorizetionInfo()");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

//        拿到当前登录的对象,并进行授权
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();   //在授权处取出认证处存储的权限信息
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

//    认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("进入了认证方法doGetAuthenticationInfo");
        UsernamePasswordToken token1 = (UsernamePasswordToken)token;
        User user = userMapper.queryUserByName(token1.getUsername());

//       用户名认证,如果用户名不正确,返回null,则在MyController.login()方法中抛出用户名不存在异常
        if(user==null){
            return null;
        }

        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession();
        session.setAttribute("loginUser", user);
//      密码验证,如果密码错误,则在MyController.login()中抛出密码错误异常
        /************************************************************
         * SimpleAuthenticationInfo构造方法的三个参数:
         *      principal: 权限信息,认证处存储的权限信息,可以在授权处取出来
         *      credentials: 密码
         *      realmName:                                      
         *************************************************************/
//        将user作为权限信息存储进去
        return new SimpleAuthenticationInfo(user, user.getPwd(), "");
    }
}

shiro中有三大对象:ShiroFileterFactoryBean, DefaultWebSecurityManager, Realm,前面我们已经定义好了我们需要的Realm,现在使用这个定义的Realm来创建这些对象:

@Configuration
public class ShiroConfig {
//    shiro三大对象:ShiroFIleterFactoryBean, DefaultWebSecurityManager, realm

//  3.创建一个ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean
    (@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        System.out.println("进入了shiroFilterFactoryBean(),这里进行了权限限定");

        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);

//        添加shiro的内置过滤器
        /*
        *  anno: 无需认证就可以访问
        * authc: 必须认证了才能访问
        * user: 必须拥有 记住我 功能才能用
        * perms:拥有某个资源的权限才能访问
        * role:有用某个角色权限才能访问
        * */
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/*", "authc");
//        filterMap.put("/user/add", "authc");
//        filterMap.put("/user/update", "authc");

//        授权,权限不够会跳转到未授权页面
        filterMap.put("/user/add", "perms[user:add]");
        filterMap.put("/user/update", "perms[user:update]");




        bean.setFilterChainDefinitionMap(filterMap);
//        设置登录的请求
        bean.setLoginUrl("/toLogin");
        
//        设置未授权的请求
//        bean.setUnauthorizedUrl("/noauth");

        return bean;
    }


//    2.创建一个DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager
    (@Qualifier("userReaml") UserReaml userReaml){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userReaml);
        return securityManager;
    }

//    1.创建realm对象,一个自定义的类
    @Bean(name = "userReaml")
    public UserReaml userReaml(){
        return new UserReaml();
    }

    @Bean
    public ShiroDialect getShiroDialect(){  //用来整合shiro和thymeleaf
        return new ShiroDialect();
    }
}

接下来就是实现控制器,处理请求:

@Controller
public class MyController {
    @RequestMapping({"/", "/index"})
    public String index(Model model){
        model.addAttribute("msg", "hello, Shiro");
        return "index";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String userName, String password, Model model){
        System.out.println("进入了login()");

        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        try{
            currentUser.login(token);
            return "index";
        }catch (UnknownAccountException uae){
            model.addAttribute("msg", "用户名不存在");
            System.out.println("用户名不存在");
            return "login";
        }catch (IncorrectCredentialsException ice){
            model.addAttribute("msg", "密码错误");
            System.out.println("密码错误");
            return "login";
        }catch (AuthenticationException ae){
            model.addAttribute("msg", "登陆失败");
            return "login";
        }

//        System.out.println(userName+"***"+password);
//        return "login";
    }

    @RequestMapping("/user/add")
    public String add(){
        Subject currentUser = SecurityUtils.getSubject();
//        可能是因为版本的原因,只有调用了Subject.isPermitted()这一类的方法时,才会走Realm里授权的方法
        if(currentUser.isPermitted("user:add")) System.out.println("具有add权限");
        else System.out.println("不具有add权限");
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(Model model){
        Subject currentUser = SecurityUtils.getSubject();
        if(currentUser.isPermitted("user:update")) {
            System.out.println("具有update权限");
            return "user/update";
        }
        else {
            System.out.println("不具有update权限");
            model.addAttribute("msg", "没有权限");
            return "index";
        }
    }

    @RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized(){
        return "权限不够";
    }
}

 下面是一个用了些thymeleaf标签的前端页面【不要忘记引入命名空间】:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<p1>这是首页</p1>
<p th:text="${msg}"></p>
<hr>
<!--很奇怪,项目刚启动时第一次链接请求会出现400的错误,返回页面进行第二次以及之后的链接就不会出现错误了-->
<span th:text="${msg}"></span>
<!--未登录的时候,显示登录-->
<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登录</a>
</div>
<!--<div shiro:hasPermission="user:add"><a th:href="@{/user/add}">add</a></div>-->
<a th:href="@{/user/add}">add</a>
<!--有相应的权限,才能看到相应的内容-->
<div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a></div>

<!--<a th:href="@{/user/add}">add</a>  |  <a th:href="@{/user/update}">update</a>-->

</body>
</html>

10 swagger

10.1简介

swagger是基于前后端分离使用的,它可以实时更新、展示后台代码信息,还能够直接测试

10.2使用

10.2.1 配置swagger

导入依赖【注意,maven版本不能过高,否则会出现错误,这里使用2.1.7.RELEASE】

        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>

配置swagger

@Configuration
@EnableSwagger2
public class SwaggerConfig {

}

编写控制器

@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    public String hello(){
        return "hello, swagger";
    }

}

 至此,swagger已经配置好了,现在可以启动项目,输入localhost:8080/swagger-ui.html,出现swagger页面,它主要由四个部分组成

 10.2.2 设置swagger

swagger页面中可以看到文档的一些说明信息,首先来完成这部分的设置,在SwaggerConfig类中配置swagger的Bean实例:

//    配置swagger的bean实例
    @Bean
    public Docket docket(Environment environment){

        return new Docket(DocumentationType.SWAGGER_2)
                .enable(flag)
                .apiInfo(apiInfo())
                .build();
    }

    private ApiInfo apiInfo(){
//        作者信息
        Contact contact = new Contact("plane", "23333", "1824271147@qq.com");

        return new ApiInfo(
                "Api Documentation title",
                "Api Documentation description",
                "1.0",
                "urn:tos",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());
    }

在实例化Docket的时候,还可以配置扫描控制器的规则,在docket()方法中做如下修改就好了

    @Bean
    public Docket docket(){

        return new Docket(DocumentationType.SWAGGER_2)
                .enable(flag)
                .apiInfo(apiInfo())
                .select()
                /*************************************************************
                 * RequestHandlerSelectors, 配置要扫描接口的方式                 *
                 *      basePackage:指定要扫描的包                              *
                 *      any():扫描全部                                         *
                 *      none():不扫描                                          *
                 *      withClassAnnotation:扫描类上的注解                      *
                 *      withMethodAnnotation:扫描方法上的注解                   *
                 **************************************************************/
                .apis(RequestHandlerSelectors.basePackage("com.plane.controller"))
//                .paths(PathSelectors.ant("/plane/**")) //这个路径下的会被过滤掉
                .build();
    }

接下来设置模块部分,也就是实体类的展示。在controller中,返回的实体类都会被扫描到,并且扫描的是实体类的public部分。编写一个User实体类,使控制器通过一个请求返回一个User

@ApiModel("用户实体类")
public class User {
    @ApiModelProperty("用户名")
    public String name;
    @ApiModelProperty("密码")
    public String password;
}
    @RequestMapping("/user")
    public User user(){
        return new User();
    }

接下来设置分组信息,每配置一个Docket的Bean实例,就相当于添加了一个分组,想添加多个分组的话那就配置多个Docket的Bean实例就好了 

    @Bean
    public Docket docket1(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("A");
    }
    @Bean
    public Docket docket2(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("B");
    }
    @Bean
    public Docket docket3(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("C");
    }

swagger可以看到项目的后台接口,这如果被用户看到是不安全的,所有还应该进行设置,使得只有在开发环境下显示页面,发布环境下不显示。通过配置文件指定环境,然后使用Environment参数获取当前环境,判定是否使用swagger

三个配置文件:

其中,application.properties中指定当前使用的配置文件,另外两个则分别指定了不同的端口号

spring.profiles.active=dev

 在docket()中判断当前环境,设置是否使用swagger

//    配置swagger的bean实例
    @Bean
    public Docket docket(Environment environment){
//        设置要显示的Swagger环境
        Profiles profiles = Profiles.of("dev", "test");
//        通过environment.acceptsProfiles判断是否处在自己设定的环境中
        boolean flag = environment.acceptsProfiles(profiles);

        return new Docket(DocumentationType.SWAGGER_2)
                .enable(flag)
                .apiInfo(apiInfo())
                .select()
                /*************************************************************
                 * RequestHandlerSelectors, 配置要扫描接口的方式                 *
                 *      basePackage:指定要扫描的包                              *
                 *      any():扫描全部                                         *
                 *      none():不扫描                                          *
                 *      withClassAnnotation:扫描类上的注解                      *
                 *      withMethodAnnotation:扫描方法上的注解                   *
                 **************************************************************/
                .apis(RequestHandlerSelectors.basePackage("com.plane.controller"))
//                .paths(PathSelectors.ant("/plane/**")) //这个路径下的会被过滤掉
                .build();
    }

 通过上面的代码,我们设置了页面文档的信息,分组信息,default组中控制器的扫描和模块部分,以及在不同环境下是否使用swagger,接下来就是测试了。

10.3 测试

首先输入localhost:8081/swagger-ui.html,在dev环境下访问swagger,如下

接下来,在application.properties中改变使用的配置文件,再访问 localhost:8082/swagger-ui.html,此时不是dev环境,页面不能访问

此时可以看到,default已经不存在分组信息中了。 

11. 异步任务和定时任务

11.1 异步任务

在Javaweb中,有很多任务是需要异步执行的,比如邮件发送这样比较耗时的操作,如果只用单线程的话在邮件发送完成前页面会卡住,这对于用户体验是十分差的,所以需要使用异步方法。

异步方法其实就是多线程。在JavaSE中有多种开启多线程的方法,但是这些方法都比较繁琐,springboot帮我们做了很多,只需要一个注解就可以实现异步了。

@Service
public class AsyncService {
    @Async //执行这个方法的时候,就开启一个线程池
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("处理完成");
    }
}
@Controller
public class HelloController {
    @Autowired
    AsyncService asyncService;

    @RequestMapping("/hello")
    @ResponseBody
    public String hello(){
        asyncService.hello();
        return "ok";
    }
}

 在这个示例中,AsyncService.hello()方法就已经被处理为异步方法了。

11.2 定时任务

定时任务,就是在设定的时间进行操作。操作时间的设定可以通过cron表达式完成。下面的示例是在周一到周日的17:11执行方法。cron表达式可上网查询。

@Service
public class SecheduledService {
             //    秒 分 时 日 月 周几
    @Scheduled(cron = "0 11 17 * * 0-7")
    public void hello(){
        System.out.println("这个方法执行了");
    }
}

12 dubbo

12.1 dubbo概述

dubbo是一款高性能、轻量级的开源JavaRPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

dubbo本身并不是一个服务软件,而是一个jar包,能够帮你的Java程序连接到zookeeper,并利用zookeeper消费、提供服务。为了让用户更好的管理监控众多的dubbo服务,官方提供了也给可视化的监控程序dubbo-admin,不够这个监控即使不装也不影响。

12.2 使用

配置好dubbo和zookeeper后,分别建provider-server项目和consumer-server项目,pom依赖如下【注意版本问题,可能会出现一些奇怪的问题】:

        <!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>

<!--        日志会冲突-->
<!--        引入zookeeper-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
<!--            排除slf4j-log4j12-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

12.2.1 provider-server

设置application.properties

server.port=8082

#服用应用名称
dubbo.application.name=provider-server
#注册中心地址  注册中心地址需要和dubbo中application.properties文件中的地址相同
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.plane.service

dubbo.config-center.timeout=25000

 在service包下建立一个TicketService接口,以及它的实现类:

package com.plane.service;

public interface TicketService {
    public String getTicket();
}
package com.plane.service;

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

@Service   //将服务发布出去
@Component //放在spring容器中
public class TicketServiceImpl implements TicketService{
    @Override
    public String getTicket() {
        return "买了一张票";
    }
}

启动zookeeper和dubbo-admin,然后再启动项目,访问localhost:7001,可以看到getTiciet()方法已经被监听到了:

 12.2.2 consumer-server

pom依赖和提供者相同

编写application.properties配置文件

server.port=8081

#消费者去哪里拿服务,需要暴露自己的名字
dubbo.application.name=consumer-service
#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

与提供者相同的包路径下也写一个TicketService接口 

package com.plane.service;

public interface TicketService {
    public String getTicket();
}

编写UserService类【注意注释用的是哪个,会有同名注释】

package com.plane.service;

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

@Service
public class UserService {
//    到注册中心拿到需要的服务
    @Reference         //引用服务,使用pom坐标,定义路径相同的接口名
    TicketService ticketService;

    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.out.println("在注册中心拿到票:"+ticket);
    }
}

 不要关闭提供者项目的运行,在consumer-server项目的test中测试运行:

@SpringBootTest
class ConsumerServerApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
        userService.buyTicket();
    }

}

 运行结果如下:

 这样,就调用到了另一个项目中的资源,实现分布式了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值