SpringBoot入门

约定大于配置

SpringBoot入门

1.SpringBoot是什么

SpringBoot并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,同时集成了大量的第三方库,就像maven整合了所有的jar包,SpringBoot整合了所有的框架

1.1.SpringBoot最核心的东西
  • 自动装配
  • 约定大于配置

2.SpringBoot的主要优点

  • Spring开发者更快入门
  • 开箱即用,提供默认配置 简化项目配置
  • 内嵌式容器简化Web项目,不用配置Tomcat
  • 没有冗余代码和XML的配置文件
  • 测试变得简单,内置了JUnit和Test等多种测试框架

3.配置SpringBoot的pom.xml文件

<!-- 父依赖 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- web场景启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- springboot单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <!-- 剔除依赖 -->
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 打包插件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

设置SpringBoot热部署(修改代码不需要重新部署项目)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>
3.1.将项目打包成jar文件

使用maven的package生命周期
如遇到错误配置打包跳过测试用例,打包成功会在target生成一个jar文件

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <!--跳过项目运行测试用例-->
        <skipTests>true</skipTests>
    </configuration>
</plugin>
3.2.SpringBoot启动图标修改

新建banner.txt放入resources目录下
图案可在https://www.runoob.com生成,拷入banner.txt就行

3.3.SpringBoot启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Springboot01HelloworldApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}

@SpringBootApplication

  • @SpringBootConfiguration
    SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;(底层是@Component
  • @EnableAutoConfiguration 自动装配
    自动配置:
  1. springboot在启动之后从classpath中搜寻META-INF/spring.factories配置文件 ,所有的自动配置类都在这个文件里面,但是不一定生效,@ConditionalOnXXX会判断条件是否成立,需要导入对应的start(也就是jar包),注解条件才会成立;
  2. 一旦这个配置类生效,这个配置类就会给容器添加各种组件,这些组件是从对应的properties类中获取的,这个类的每个属性又是和application.yml配置文件绑定的
  3. 然后通过反射实例化被 @Configuration注释的配置类 , 并将其汇总并加载到IOC容器中,也就实现了自动装配。
  • @ComponentScan
    扫描启动类路径和子路径的所有组件,并将其加载进IOC容器

SpringApplication

这个类主要做了以下四件事情:

  1. 推断应用的类型是普通的项目还是Web项目
  2. 查找并加载所有可用初始化器 , 设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中
  4. 推断并设置main方法的定义类,找到运行的主类

4.yaml语法

SpringBoot优先级:yml>yaml>propertes
yml和yaml语法是一样的,高优先级先加载,低优先级的会覆盖高优先级的

  • application.properties
    • 语法结构:key=value
  • application.yml
    • 语法结构:key: 空格 value

配置文件的作业:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了
如:server.port=8081
yml中为:

server:
 port:8081

yml要求
语法要求严格
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。

通过@ConfigurationProperties(prefix = "person")去绑定.yml文件中的对象,
而.properties需先使用@PropertySource(value = "classpath:person.properties")读取配置文件,再使用@Value("name")绑定对于的字面量

5.JSR303数据校验和多环境切换

5.1.数据校验

依赖

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
</dependency>

使用@Validated使数据校验注解生效,再使用@Email(message="邮箱格式错误")或者@Null等注解进行数据校验,保证数据的正确性

5.2.多环境切换

例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件
通过配置spring.profiles.active=dev切换到开发环境

配置文件加载顺序

1.file:./config/
2.file:./
3.classpath:./config/
4.classpath:./

也就是

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

SpringBoot会加载所有的配置文件,高优先级的覆盖低优先级的,再互补其他配置
config下的优先级会高于根目录、高优先级会覆盖低优先级的配置文件、后期运维外部指定的配置文件优先级最高

运维小技巧
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
可以指定配置文件(优先级最高)

6.日志

注意:在生产上严禁使用System.out输出,性能太低,原因是System.out输出会导致线程等待(同步),而使用Logger输出线程不等待日志的输出(异步

依赖:

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

日志级别:trace<debug<info<warn<error

Logger logger =  LoggerFactory.getLogger(getClass());
    @Test
    void contextLoads() {
        logger.trace("trace日志。。。");
        logger.debug("debug日志。。。");
        //SpringBoot默认级别是info,只会打印info及后面的日志,也是root级别
        logger.info("info日志。。。");
        logger.warn("warn日志。。。");
        logger.error("error日志。。。");
    }

可以调整日志级别,设置只有高于此级别的才输出
logging.level.com.lvboaa=trace 表示把com.lvboaa包下面的所有类级别都设置为trace,能打印比trace优先级高的日志

日志信息写入文件:

#指定日志文件的名字,不设置path就在项目根目录下,name优先级高,只会写入一个文件
logging.file.name=logging.log
#会将log文件放在项目在电脑上的根目录,创建log文件夹,及spring.log文件
logging.file.path=/log

如果想使用自己的日志配置,只需要将配置文件(如:log-back.xml)放在类路径(classpath:)下即可覆盖默认配置

二、SpringBoot中级

1.数据库

使用Spring Data连接数据库,SQL和NoSQL都能连接

1.1.使用SpringBoot的默认数据库

HikariDataSource号称JavaWeb速度最快的数据库,相比于传统的C3P0,DBCP更加优秀

1.2.使用阿里巴巴的Druid数据源

依赖: 两种都可以,下面是和springboot适配了的

<!--druid-->
    <!--<dependency>-->
        <!--<groupId>com.alibaba</groupId>-->
        <!--<artifactId>druid</artifactId>-->
        <!--<version>1.1.21</version>-->
    <!--</dependency>-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>

比其他的数据库多了监控filters:stat、日志filters:log4j(日志需要导入log4j依赖,不然会报错)和防止SQL注入filters:wall(配置文件配置)

在配置文件加入东西可进入http://localhost:8080/druid/index.html监控sql

spring:
  datasource:
  druid:
      initial-size: 5 #连接池初始化大小
      min-idle: 10 #最小空闲连接数
      max-active: 20 #最大连接数
      # 配置监控统计拦截的 Filter,去掉后监控界面 SQL 无法统计,wall 用于防火墙
      filters: stat,wall,slf4j
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不统计这些请求数据
      stat-view-servlet: #访问监控网页的登录用户名和密码
        url-pattern: /druid/*
        reset-enable: false
        #  allow: 127.0.0.1 
        login-username: admin #登录账号
        login-password: 12345  #登录密码
1.3.整合mybatis
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

可以直接使用@Mapper注解,在Dao层接口的方法上面写SQL语句

1.4.整合thymeleaf
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

2.Swagger

2.1.Swagger简介

主要实现是前后端分离的接口交互

  • 号称世界上最流行的API框架
  • Restful Api文档自动生成=>Api文档和Api定义同步更新
  • 直接运行,可以在线测试Api接口
  • 支持多种语言
2.2.SpringBoot整合Swagger
  • 1.导入依赖
<!--Swagger-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

一个空的Web项目至少有一个/error的请求(error page)

** 配置Swagger-ui的界面

@Configuration
public class SwaggerConfig {

    //配置Swagger的Docket的bean实例
    @Bean
    public Docket getDocket(){
        return new Docket(DocumentationType.OAS_30);
                //.apiInfo(apiInfo());
    }

    //配置Swagger信息 apiInfo() Swagger的页面信息
    private ApiInfo apiInfo(){
        Contact contact = new Contact("Lvboaa", "https://www.baidu.com", "2484420621@qq.com");//作者信息
        return new ApiInfo(
                "Lvboaa SwaggerApi Document",
                "生而为人,我很抱歉。",
                "1.0",
                "https://www.baidu.com",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());

    }
}

3.定时任务

  1. 创建定时任务
@Service
public class ScheduledService {
   
   //秒   分   时     日   月   周几
   //0 * * * * MON-FRI
   //注意cron表达式的用法;
   @Scheduled(cron = "0 * * * * 0-7")
   public void hello(){
       System.out.println("hello.....");
  }
}
  1. 在主程序上增加@EnableScheduling 开启定时任务功能
@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {

   public static void main(String[] args) {
       SpringApplication.run(SpringbootTaskApplication.class, args);
  }

}

cron表达式

http://www.bejson.com/othertools/cron/

常用表达式

(1)0/2 * * * * ?   表示每2秒 执行任务
(1)0 0/2 * * * ?   表示每2分钟 执行任务
(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触发

4.邮件任务

配置步骤

  • 需要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送
  1. 引入依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  1. 配置文件
spring.mail.username=24736743@qq.com
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

获取qq邮件授权码
在这里插入图片描述
3. 单元测试

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
   //邮件设置1:一个简单的邮件
   SimpleMailMessage message = new SimpleMailMessage();
   message.setSubject("通知-明天来狂神这听课");
   message.setText("今晚7:30开会");

   message.setTo("24736743@qq.com");
   message.setFrom("24736743@qq.com");
   mailSender.send(message);
}

@Test
public void contextLoads2() throws MessagingException {
   //邮件设置2:一个复杂的邮件
   MimeMessage mimeMessage = mailSender.createMimeMessage();
   MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

   helper.setSubject("通知-明天来狂神这听课");
   helper.setText("<b style='color:red'>今天 7:30来开会</b>",true);

   //发送附件
   helper.addAttachment("1.jpg",new File(""));
   helper.addAttachment("2.jpg",new File(""));

   helper.setTo("24736743@qq.com");
   helper.setFrom("24736743@qq.com");

   mailSender.send(mimeMessage);
}

5.SpringSecurity

做网站,安全应该在设计之初就考虑

核心:把权限赋值给角色而不是具体的用户,用户只需要通过获得角色就能获得相应的权限

5.1、认识SpringSecurity
  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)

  • 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

  • 授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。这个概念是通用的,而不是只在Spring Security 中存在。
5.2、配置使用SpringSecurity

配置步骤

  1. 引入依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 编写配置类
@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");

        //没有权限默认回到登录页面 相当于controller里面的return "login";
        //默认是安全框架自己的登录页面 http.formLogin(); 成功就回到首页 "/"
        //设置没有权限返回自己登录的页面  loginPage("/toLogin")
        //设置登录页面提交表单请求的路径 loginProcessingUrl("/login"); 认证成功默认到主页"index"
        //设置认证成功到达的页面 defaultSuccessUrl("/level1/1")
        // 输入框的名字必须:username和password必须和源码匹配
        http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");

        //注销功能http.logout();默认回到框架的登录页面
        //清空所有cookies和session http.logout().deleteCookies("remove").invalidateHttpSession(true);
        //设置注销后去的页面
        http.logout().logoutSuccessUrl("/");

        //防止攻击,get是明文传送 csrf
        //应为登出logout是get请求,可能会有跨站伪造请求,默认csrf是开启的
        http.csrf().disable(); //登出失败可能的原因未关闭

        //开启记住我功能 cookie 默认保存两周 框架登录页面的Remember
        //匹配自己登录页面记住我的name
        http.rememberMe().rememberMeParameter("remember");

    }

    //认证
    //密码编码,防止明文显示
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {


        //数据正常应该从数据库读
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("lvbo").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
    }
}

6.Shiro

6.1.简介

也是一个安全框架,可以完成认证、授权、加密、会话管理、web继承和缓存等

Shiro最主要的三个对象

  • Subject:当前用户
  • SecurityManager:安全管理器,关联Realm
  • Realm:连接数据的桥梁,将数据库和Shiro相连
    • 自定义Realm实现认证、授权
6.2.具体实现

Shiro文档的QuickStart


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);

        // get the currently executing user:
        //获取当前用户对象Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //通过该用户拿到Session
        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 + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //判断当前用户是否被认证
        if (!currentUser.isAuthenticated()) {
            //token 令牌
            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 more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {//最大的认证异常:是否成功
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        //获取认证信息码
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        //测试角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        //测试是否有权限 粗粒度:简单
        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.");
        }

        //a (very powerful) Instance Level permission:
        //细粒度:更高的
        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!");
        }

        //all done - log out!
        //注销功能
        currentUser.logout();

        System.exit(0);
    }
}

配置类:ShrioConfig.java

@Configuration
public class ShiroConfig {


    //ShiroFilterFactoryBean:3
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactory(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(securityManager);
        //添加shiro的内置过滤器
        /*
            anno:无需认证就可访问
            authc:必须认证了才可访问
            user:必须拥有记住我 才能访问
            perms:拥有某个资源的权限才能访问
            role:拥有某个权限才能访问
         */
        Map filterMap = new LinkedHashMap<>();
        //一般前面是接口路径,后面是权限
//        filterMap.put("/user/add","authc");
//        filterMap.put("/user/update","authc");
        //授权,没有授权的会跳到未授权页面
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");
        //注销 注销成功回到`/` 不建议
        //filterMap.put("/logout", "logout");
        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);
        //设置登录页面
        bean.setLoginUrl("/toLogin");

        //设置未授权的页面
        bean.setUnauthorizedUrl("/unauthor");

        return bean;
    }
    //DefaultWebSecurityManager:2
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurity(@Qualifier("userRealm")UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    //创建realm对象,需要自定义类:1
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

注销代码实现:logout

@RequestMapping("/logout")
public String logout(Model model){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
    return "user/logout";
}

登录授权认证:login

@RequestMapping("/login")
public String login(String username,String password,Model model){
    //获取当前用户
    Subject subject = SecurityUtils.getSubject();
    //封装用户登录数据
    UsernamePasswordToken token = new UsernamePasswordToken(username,password);
    try{
        //进入Shiro框架,执行授权和认证
        subject.login(token);
        return "index";
    }catch (UnknownAccountException e){
        model.addAttribute("msg","用户名不存在");
        return "login";
    }catch (IncorrectCredentialsException e){
        model.addAttribute("msg","密码错误");
        return "login";
    }
}

使用自定义的Realm: UserRealm

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 user = (User) subject.getPrincipal();//拿到下面认证返回的user对象
        if(user.getName().equals("root")){
            //添加多个授权

            info.addStringPermissions(Arrays.asList(user.getAuthor().split(",")));
        }else {
            //添加授权 一个授权
            info.addStringPermission(user.getAuthor());
        }

        //获得数据库角色,根据角色授权
        /*
        User user = (User) principalCollection.getPrimaryPrincipal();
        List<Role> roleList = roleservice.getRoleListByUserName(user.getUserName());
        List<Permission> permissionList = null;
        if (roleList.size() > 0) {
            //添加角色
            for (Role role : roleList) {
                authorizationInfo.addRole(role.getRole());
                permissionList = permissionService.getPermissionListByRoleId(role.getRoleId());
                for (Permission permission : permissionList) {
                    //添加权限
                    authorizationInfo.addStringPermission(permission.getPermission());
                }
            }
        }
         */
        return info;
    }

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

        //用户名、密码,从数据库中取
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        User user = userService.querUserByName(userToken.getUsername());
        if(user == null){
            return null;//无用户名 自动抛出异常UnknownAccountException
        }

        //密码验证 shiro自己认证 之后进入授权
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }
}

7.Dubbo和Zookeeper集成

微服务解决的四个问题

  • 1.API网关,服务路由
  • 2.Http,RPC框架,异步调用
  • 3.服务注册与发现,高可用
  • 4.熔断机制,服务降级

为什么需要解决这些问题?
网络是不可靠的!

7.1.简介

什么是分布式

  • 分布式系统是若干独立计算机的集合,但是这些计算机对于用户来说就像单个相关系统
    • 需要实现负载均衡(Nginx代理服务器、SpringCloud等)
  • 由一组通过网络进行通信、为了完成共同任务而协调工作的计算机结点组成的系统,其目的是利用更多的机器,处理更多的数据
  • 是建立在网络上的软件系统
  • 明确:只有当单个节点处理能力无法满足计算、储存时才会考虑分布式,因为会引入很多单系统没有的问题
  • MVC模式是将应用分成几个不同的模块,但是公共模块不能重用,分布式架构就在MVC的模式上将公共的模块提取出来
  • 核心:分布式服务框架(RPC)

RPC

推荐文章:https://www.jianshu.com/p/2accc2840a1b

RPC:远程过程调用(Remote Procedure Call),一种技术思想、而不是规范

  • 本地过程调用:本地方法A() 调用本地方法B()
  • 远程过程调用:一个机器上的方法A() 调用另一台机器上的方法B()

RPC基本原理:
在这里插入图片描述

步骤解析:
在这里插入图片描述
核心:
* 通讯:http协议等
* 序列化:方便在网络上数据传输

7.2. Dubbo

简介

基于Java的高性能、轻量级开源RPC框架

核心

  • 面向接口的远程调用
  • 智能容错和负载均衡
  • 服务自动注册和发现

侵入式:要求用户代码“知道”框架的代码,表现为用户代码需要继承框架提供的类。不利于代码的复用。但可以和框架更好的结合、充分发挥框架的作用(struts)
非侵入式:不需要用户代码引入框架代码的信息,从类的编写者角度来看,察觉不到框架的存在。没有过多的依赖,但与用户代码的互动比较困难(spring)

Doubbo是非侵入式

运行原理
在这里插入图片描述

  • 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

  • 服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

  • 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

  • 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明:

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

代码实现

  • provider-server
    依赖
<!--dubbo-->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.3</version>
</dependency>
<!--zookeeper的client-->
<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>

配置:

server.port=8081
#服务应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务器要被注册
dubbo.scan.base-packages=com.lvboaa.service

注册服务

@Service //可以被扫描到,在项目启动时就自动注册到注册中心(dubbo的)
public class TicketServiceImpl implements TicketService {
    @Override
    public String getTocket() {
        return "买了张票。。。";
    }
}
  • consumer-server
    依赖和provider一样
    配置
server.port=8082
#消费者去哪拿服务需要暴露自己的名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

获取服务:需先将TicketService复制到同名包下

@Service//将service注入Spring容器
public class UserService {

    //想拿到provider提供的票,去注册中心拿到服务
    @Reference //(dubbo的)
    TicketService ticketService;
    public void buyTicket(){
        String tocket = ticketService.getTocket();
        System.out.println(tocket);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值