第一章-Spring基础

本章主要学习了spring的一些核心组件、初始化一个Spring项目以及spring概览。

快速开始

spring是什么

Spring的核心是提供一个容器,通常称为Spring应用程序上下文,用于创建和管理应用程序组件。
Alt text

初始化Spring应用程序

使用Spring Initializr的几种方法如下:

  • http://start.spring.io
  • curl 命令
  • 使用Spring引导命令行界面
  • Spring Tool Suite new project
  • IntelliJ IDEA new project
  • NetBeans new project

spring项目架构图:
Alt text

写一个helloword

  1. 定义controller层
    Alt text

  2. 定义view层
    Alt text

  3. 测试controller类
    Alt text

  4. 构建和运行spring应用
    Alt text

Spring概览

Spring核心框架是Spring领域中其他一切的基础。它提供了核心容器和依赖注入框架。

  1. Spring Boot
    优点:
  • 启动依赖项和自动配置
  • 执行器提供对应用程序内部工作方式的运行时洞察
  • 环境属性的灵活规范
  • 额外的测试支持
  1. Spring Data
    Spring Data提供了一些非常惊人的功能:将应用程序的数据存储库定义为简单的Java接口,在定义驱动如何存储和检索数据的方法时使用命名约定。

  2. Spring Security
    Spring Security解决了广泛的应用程序安全性需求,包括身份验证、授权和API安全性。

  3. Spring Integration和Spring Batch
    Spring Integration解决了实时集成,即数据在可用时进行处理。
    Spring Batch解决了成批集成的问题,允许在一段时间内收集数据,直到某个触发器(可能是一个时间触发器)发出信号,表示该处理一批数据了。

  4. Spring Cloud
    Spring Cloud是一组用Spring开发云本地应用程序的项目。

部署web应用

信息展示

Alt text

定义domain类

Alt text

创建controller类

Alt text

设计页面

[案例]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 添加依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2. 页面显示变量
单个变量:
<p th:text="${message}">placeholder message</p>
对象:
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>

form表单提交

  1. 页面

    [案例]
    1
    2
    3
    4
    5
    <form method="POST" th:action="@{/orders}" th:object="${order}">
    Name:<input type="text" th:field="*{name}"/>
    Street address:<input type="text" th:field="*{street}"/>
    ...
    </form>
  2. 业务端代码

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // controller类
    @Slf4j
    @Controller
    @RequestMapping("/orders")
    public class OrderController {
    @PostMapping
    public String processOrder(Order order) {
    log.info("Order submitted: " + order);
    return "redirect:/";
    }
    }

    // domain类
    @Data
    public class Order {
    private String name;
    private String street;
    ...
    }

form表单验证

定义验证规则

[案例]
1
2
3
4
5
6
7
8
9
10
@Data
public class Order {
@NotBlank(message="名字是必需的")
private String name;
@CreditCardNumber(message="不是有效的信用卡号码")
private String ccNumber;
@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$", message="必须是格式 MM/YY")
private String ccExpiration;
...
}

在表单绑定时执行验证(@Valid)

[案例]
1
2
3
4
5
6
7
8
@PostMapping
public String processOrder(@Valid Order order, Errors errors) {
if (errors.hasErrors()) {
return "orderForm";
}
log.info("Order submitted: " + order);
return "redirect:/";
}

显示验证错误信息

Alt text

使用视图控制器

[案例]
1
2
3
4
5
6
7
8
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}

选择视图模板库

Alt text

使用data

使用JDBC读写数据

  1. 添加依赖

    [案例]
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
  2. 定义JDBC存储库

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    public interface IngredientRepository {
    Iterable<Ingredient> findAll();
    Ingredient findOne(String id);
    Ingredient save(Ingredient ingredient);
    }

    @Repository
    public class JdbcIngredientRepository implements IngredientRepository {
    private JdbcTemplate jdbc;
    @Autowired
    public JdbcIngredientRepository(JdbcTemplate jdbc) {
    this.jdbc = jdbc;
    }
    ...

    // 使用JdbcTemplate查询数据库
    @Override
    public Iterable<Ingredient> findAll() {
    return jdbc.query("select id, name, type from Ingredient", this::mapRowToIngredient);
    }
    private Ingredient mapRowToIngredient(ResultSet rs, int rowNum) throws SQLException {
    return new Ingredient(
    rs.getString("id"),
    rs.getString("name"),
    Ingredient.Type.valueOf(rs.getString("type")));
    }
    // 查询单条记录
    @Override
    public Ingredient findOne(String id) {
    return jdbc.queryForObject(
    "select id, name, type from Ingredient where id=?",
    new RowMapper<Ingredient>() {
    public Ingredient mapRow(ResultSet rs, int rowNum)
    throws SQLException {
    return new Ingredient(
    rs.getString("id"),
    rs.getString("name"),
    Ingredient.Type.valueOf(rs.getString("type")));
    };
    }, id);
    }
    // 保存
    @Override
    public Ingredient save(Ingredient ingredient) {
    jdbc.update(
    "insert into Ingredient (id, name, type) values (?, ?, ?)",
    ingredient.getId(),
    ingredient.getName(),
    ingredient.getType().toString());
    return ingredient;
    }
    }
  3. 定义controller类

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Controller
    @RequestMapping("/design")
    @SessionAttributes("order")
    public class DesignTacoController {
    private final IngredientRepository ingredientRepo;
    @Autowired
    public DesignTacoController(IngredientRepository ingredientRepo) {
    this.ingredientRepo = ingredientRepo;
    }
    @GetMapping
    public String showDesignForm(Model model) {
    List<Ingredient> ingredients = new ArrayList<>();
    ingredientRepo.findAll().forEach(i -> ingredients.add(i));
    Type[] types = Ingredient.Type.values();
    for (Type type : types) {
    model.addAttribute(type.toString().toLowerCase(),
    filterByType(ingredients, type));
    }
    return "design";
    }
    ...
    }

使用Spring data JPA持久化数据

  1. 添加依赖

    [案例]
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
  2. 定义实体bean

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Data
    @Entity
    public class Taco {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    @NotNull
    @Size(min=5, message="Name must be at least 5 characters long")
    private String name;
    private Date createdAt;
    @ManyToMany(targetEntity=Ingredient.class)
    @Size(min=1, message="You must choose at least 1 ingredient")
    private List<Ingredient> ingredients;
    @PrePersist
    void createdAt() {
    this.createdAt = new Date();
    }
    }
  3. 定义JPA存储库

    [案例]
    1
    public interface IngredientRepository extends CrudRepository<Ingredient, String> { }
  4. 自定义JPA存储库
    一些关键语法:

  • IsAfter, After, IsGreaterThan, GreaterThan
  • IsNull, Null
  • IsIn, In
  • IsBetween, Between

    OrderRepository:
    [案例]
    1
    2
    3
    4
    5
    6
    // 方式一:根据deliveryZip字段查询
    List<Order> findByDeliveryZip(String deliveryZip);
    // 方式二:自定义sql
    @Query("Order o where o.deliveryCity='Seattle'")
    List<Order> readOrdersDeliveredInSeattle();
    ...

使用Security

集成spring security

添加依赖

[案例]
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

配置Spring Security

[案例]
1
2
3
4
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

基于内存的用户存储

[案例]
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("buzz")
.password("infinity")
.authorities("ROLE_USER")
.and()
.withUser("woody")
.password("bullseye")
.authorities("ROLE_USER");
}

基于jdbc的用户存储

[案例]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery(
"select username, authority from UserAuthorities " +
"where username=?")
.passwordEncoder(new StandardPasswordEncoder("53cr3t");
}

passwordEncoder()有几种加密方式:

  • BCryptPasswordEncoder
  • NoOpPasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder
  • StandardPasswordEncoder

LDAP-backed用户存储

[案例]
1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("passcode");
}

定制用户身份验证

  1. 定义用户域和持久性

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @Entity
    @Data
    @NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
    @RequiredArgsConstructor
    public class User implements UserDetails {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private final String username;
    private final String password;
    private final String fullname;
    ...
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }
    @Override
    public boolean isAccountNonExpired() {
    return true;
    }
    @Override
    public boolean isAccountNonLocked() {
    return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
    return true;
    }
    @Override
    public boolean isEnabled() {
    return true;
    }
    }
  2. 创建用户详细信息服务

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Service
    public class UserRepositoryUserDetailsService implements UserDetailsService {
    private UserRepository userRepo;
    @Autowired
    public UserRepositoryUserDetailsService(UserRepository userRepo) {
    this.userRepo = userRepo;
    }
    @Override
    public UserDetails loadUserByUsername(String username)
    throws UsernameNotFoundException {
    User user = userRepo.findByUsername(username);
    if (user != null) {
    return user;
    }
    throw new UsernameNotFoundException(
    "User '" + username + "' not found");
    }
    }
  3. 修改配置类configure()方法

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
    .userDetailsService(userDetailsService)
    .passwordEncoder(encoder()); // 密码加密
    }

    @Bean
    public PasswordEncoder encoder() {
    return new StandardPasswordEncoder("53cr3t");
    }
  4. 注册用户

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Controller
    @RequestMapping("/register")
    public class RegistrationController {
    private UserRepository userRepo;
    private PasswordEncoder passwordEncoder;
    public RegistrationController(
    UserRepository userRepo, PasswordEncoder passwordEncoder) {
    this.userRepo = userRepo;
    this.passwordEncoder = passwordEncoder;
    }
    @GetMapping
    public String registerForm() {
    return "registration";
    }
    @PostMapping
    public String processRegistration(RegistrationForm form) {
    userRepo.save(form.toUser(passwordEncoder));
    return "redirect:/login";
    }
    }

request请求安全验证

配置request请求规则

[案例]
1
2
3
4
5
6
7
8
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.hasRole("ROLE_USER")
.antMatchers(“/”, "/**").permitAll();
}

Alt text

创建自定义登录页面

  1. 配置configure()

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()
    .antMatchers("/design", "/orders")
    .access("hasRole('ROLE_USER')")
    .antMatchers(“/”, "/**").access("permitAll")
    .and()
    .formLogin()
    .loginPage("/login")
    .loginProcessingUrl("/authenticate")
    .usernameParameter("username") // 表单字段:user
    .passwordParameter("password"); // 表单字段:pwd
    }
  2. 在WebConfig中声明它为一个视图控制器

    [案例]
    1
    2
    3
    4
    5
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("home");
    registry.addViewController("/login");
    }
  3. 登录页面

    [案例]
    1
    2
    3
    4
    5
    6
    7
    <form method="POST" th:action="@{/login}" id="loginForm">
    <label for="username">Username: </label>
    <input type="text" name="username" id="username" /><br/>
    <label for="password">Password: </label>
    <input type="password" name="password" id="password" /><br/>
    <input type="submit" value="Login"/>
    </form>
  4. 防止跨站请求伪造
    (1)配置configure()

    [案例]
    1
    2
    3
    http.and()
    .csrf()
    .disable()

    (2)前端页面加

    [案例]
    1
    <input type="hidden" name="_csrf" th:value="${_csrf.token}"/>

使用配置属性

微调自动配置

理解Spring的环境抽象

Alt text

  1. application.properties

    [案例]
    1
    server.port=9090
  2. application.yml

    [案例]
    1
    2
    server:
    port: 9090
  3. 命令行

    [案例]
    1
    $ java -jar tacocloud-0.0.5-SNAPSHOT.jar --server.port=9090

配置数据源

application.yml

[案例]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
datasource:
url: jdbc:mysql://localhost/tacocloud
username: tacodb
password: tacopassword
driver-class-name: com.mysql.jdbc.Driver
# 初始化数据脚本
schema:
- order-schema.sql
- ingredient-schema.sql
- taco-schema.sql
- user-schema.sql
data:
- ingredients.sql
# JNDI
jndi-name: java:/comp/env/jdbc/tacoCloudDS

配置嵌入式服务器

JDK的keytool命令行实用工具:

[案例]
1
$ keytool -keystore mykeys.jks -genkey -alias tomcat -keyalg RSA

启用HTTPS:application.yml

[案例]
1
2
3
4
5
6
server:
port: 8443
ssl:
key-store: file:///path/to/mykeys.jks
key-store-password: letmein
key-password: letmein

配置日志记录

[xml配置方式]
1
2
3
4
5
6
7
8
9
10
11
12
13
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="root" level="INFO"/>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
[application.yml配置方式]
1
2
3
4
5
6
logging:
path: /var/logs/
file: TacoCloud.log
level:
root: WARN
org.springframework.security: DEBUG

使用特殊的属性值

在设置属性时,您不仅可以将它们的值声明为硬编码的字符串和数值。相反,可以从其他配置属性派生它们的值。
welcome: You are using ${spring.application.name}.

创建自己的配置属性

  1. 定义一个属性类

    [案例]
    1
    2
    3
    4
    5
    6
    7
    @Component
    @ConfigurationProperties(prefix="taco.orders")
    @Data
    @Validated
    public class OrderProps {
    private int pageSize = 20;
    }
  2. 引用,以controller为例

    [案例]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Controller
    @RequestMapping("/orders")
    @SessionAttributes("order")
    public class OrderController {
    private OrderRepository orderRepo;
    private OrderProps props; // 属性类
    public OrderController(OrderRepository orderRepo,
    OrderProps props) {
    this.orderRepo = orderRepo;
    this.props = props;
    }
    ...
    @GetMapping
    public String ordersForUser(
    @AuthenticationPrincipal User user, Model model) {
    Pageable pageable = PageRequest.of(0, props.getPageSize());
    model.addAttribute("orders",
    orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
    return "orderList";
    }
    ...
    }

配置profiles

激活profile=prod环境的配置文件。

[application.yml]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
profiles:
active:
- prod

spring:
datasource:
url: jdbc:mysql://localhost/tacocloud
username: tacouser
password: tacopassword
logging:
level:
tacos: WARN
---
spring:
profiles: prod
datasource:
url: jdbc:mysql://localhost/tacocloud
username: tacouser
password: tacopassword
logging:
level:
tacos: WARN