SpringBoot入门

Posted by 小拳头 on Sunday, August 15, 2021

Spring Boot is an open source Java-based framework used to create a micro Service. It is developed by Pivotal Team and is used to build stand-alone and production ready spring applications. Micro Service is an architecture that allows the developers to develop and deploy services independently. Each service running has its own process and this achieves the lightweight model to support business applications.

Hello World

SpringBoot生成非常简单, 直接新建一个Spring Initializr project并配置. 比如我们建一个名叫helloworld的项目, 就会生成HelloworldApplication的类, 这个类下就包含了启动项目的方法. 在同一个包下, 就可以新建其他包, 写controller等等.

SpringBoot可以用yaml文件进行配置, 比如我们配置一个application.yaml文件:

person:
  name: zhangsan
  age: 20
  happy: false
  birth: 2000/01/01
  maps: {k1: v1, k2: v2}
  lists:
    - code
    - eat
    - sleep
  dog:
    name: 柯基
    age: 1

实际上就是给实体类的字段进行了配置, 在实体类上加入注解即可. prefix对应是yaml文件的前缀, 而Dog类直接用了Value进行字段的初始化.

@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String, Object> maps;
    private List<Object> lists;
    private Dog dog;
}

@Component
public class Dog {

    @Value("狗")
    private String name;
    @Value("3")
    private Integer age;
}

如果用@Autowired注入这两个类, 打印结果如下, 也就是说Person类下的dog实际上已经是在字段初始化后再进行赋值的结果了.

Dog{name='狗', age=3}
Person{name='zhangsan', age=20, happy=false, birth=Sat Jan 01 00:00:00 CST 2000, maps={k1=v1, k2=v2}, lists=[code, eat, sleep], dog=Dog{name='柯基', age=1}}

yaml还支持多环境配置, 通过---分开不同环境.

server:
 port: 8081
spring:
 profiles:
   active: prod
---
server:
 port: 8083
spring:
 profiles: dev
---
server:
 port: 8084
spring:
 profiles: prod

给容器中自动配置类添加组件的时候, 会从properties类中获取属性, 我们只需要在配置文件中指定这些属性的值即可. 源码中的xxxAutoConfigurartion就是自动配置类(配置类中的Conditionalxxx注解会控制配置类是否生效), 而xxxProperties封装了配置文件中的相关属性. 在配置文件中加入debug=true, 就能打印所有加载的配置.

通过@Validated注解, 可以给数据进行JSR303(Java Specification Requests)数据校验

Web开发

静态资源导入

可以通过webjars处理, 也可以放在resources下的resources/static/public目录, 优先级依次降低. 而templates中一般放动态页面, 需要通过controller访问. 动态网页传值时需要用模板引擎, springboot推荐的是thymyleaf, 导入如下maven依赖即可.

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
</dependency>

编写controller:

@Controller
public class IndexController {

    @RequestMapping("/test")
    public String test(Model model) {
        model.addAttribute("msg", "<h1>hello springboot</h1>");
        model.addAttribute("users", Arrays.asList("zhang3", "li4"));
        return "test";
    }
}

html文件中通过th:${}去取model中传入的值. text原封不动地输出了msg中的文本, 而utext标签下的文本把<h1>标签转义了. thymyleaf还可以通过each去读取一个list中的元素.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:text="${msg}">123</div>
<div th:utext="${msg}">123</div>
<div th:each="user:${users}" th:text="${user}"></div>
</body>
</html>

配置MVC

可以简单地用以下类测试, 并在class DispatcherServlet-method doDispatch中打断点, 可以看到this-viewResolvers, 也就是视图解析器实例加载的解析器下包含了我们的MyMvcConfig. 除此之外还可以看到其他的视图解析器, 比如我们刚才配置的ThymeleafViewResolver.

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    /**
     * 配置自己的视图解析器
     * @return 自定义的视图解析器class
     */
    @Bean
    public ViewResolver myViewResolver() {
        return new MyViewResolver();
    }

    /**
     * 自定义的视图解析器
     */
    public static class MyViewResolver implements ViewResolver {
        @Override
        public View resolveViewName(String viewName, Locale locale) {
            return null;
        }
    }
}

更好的方式是去实现WebMvcConfigurer的配置.

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/kuang").setViewName("test");
}

官方文档特别强调不能加入@EnableWebMvc配置类, 否则自动配置会因为condition失效

整合数据库

我做实验的时候, spring-boot-starter-parent已经到了2.5.4. 会无法自动注入DataSource, 报错的信息是如下, 所以导入对应版本的依赖即可.

Cannot resolve org.springframework:spring-tx:5.3.9
Cannot resolve com.zaxxer:HikariCP:4.0.3

配置数据库:

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

配置好吼, 就可以注入DataSource, 证明我们的数据源已经被自动配置了. 接下来就可以通过注入JdbcTemplate来进行数据库的CRUD. 比如jdbcTemplate.queryForList(sql);/jdbcTemplate.update(sql);等等操作.

还可以整合Mybatis, 都是配置即可, 我选Mybatis简单了解. 首先导入包, 我们可以发现artifactid的开头是mybatis-spring-boot-xxx, 而不是spring-boot-xxx, 说明这个不是springboot官方提供的包.

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

mybatis需要在application文件中额外配置如下信息, 直接用即可.

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

SpringSecurity

用SpringSecurity可以帮助实现登陆的验证.

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

首先需要一个controller来做实验, 子页面分别有level1/level2/level3构成.

@Controller
public class RouterController {

    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }

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

    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
        return "views/level1/"+id;
    }

    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id){
        return "views/level2/"+id;
    }

    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id){
        return "views/level3/"+id;
    }
}

写一个类来拦截请求, 分配权限. 测试的时候就会发现, 对应的用户才能访问对应的资源文件. BCryptPasswordEncoder是将密码加密的方法, 业务中通常都会对敏感信息进行加密处理.

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        http.formLogin();
        // 开启注销功能, 注销成功则跳转
        http.logout().logoutSuccessUrl("/");
        // 开启记住我功能
        http.rememberMe();
    }

    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
                .and()
                .withUser("lisi").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3")
                .and()
                .withUser("wangwu").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2");
    }
}

很多情况下, 我们更希望用自己写的登录页面, 也可以通过配置复用SpringSecurity的验证逻辑, 比如配置http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("login");, 登陆时就会走login. xxxParameter方法可以用来传递参数. rememberMe方法也是同样的道理, 用rememberMeParameter可以传递这个记住我的参数, 用checkbox组件捕获即可.

Shiro

看官方的10 Minute Tutorial on Apache Shiro即可. 直接上手做springboot继承shiro实验. 首先搭建一个index主页做测试, 测试的时候要记住先不导入shiro, 否则/会被重定向到没实现的登陆界面. springboot整合也看官方的sample, 防止踩坑.

Swagger

生成api文档, 跳过

异步/邮件/定时任务

  • 异步: 在@Service注解的类下的方法打上@Async注释, 再在主程序上加上@EnableAsync注解开启异步功能.
  • 定时: @EnableScheduling开启定时job, @Scheduled控制类执行时间, 同样需要给service类打上@Service注解
  • 邮件: 需要spring-boot-starter-mail启动器, 桶sender发送配置好的message

整合redis

加入NoSQL的redis依赖, 直接看配置类. 可以看到RedisTemplate<Object, Object>的key-value都是Object, 但是只有在redisTemplate没有时才生效, 所以我们可以自定义. 通常我们还要自己去实现RedisTemplate的序列化方式, 去序列化传入的对象. 用redisTemplate的ops系列方法操作get/set redis即可.

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

Dubbo+zookeeper

Dubbo可以让服务被注册与消费, zookeeper做服务治理.

参考

  1. SpringBoot-狂神说Java
  2. Spring Boot-Introduction

comments powered by Disqus