SpringBootWeb入门

什么是SpringBoot

Spring Boot是一个构建在Spring框架顶部的项目。它提供了一种简便,快捷的方式来设置,配置和运行基于Web的简单应用程序。

它是一个Spring模块,提供了 RAD(快速应用程序开发)功能。它用于创建独立的基于Spring的应用程序,因为它需要最少的Spring配置,因此可以运行。

简而言之,Spring Boot是 Spring Framework 和 嵌入式服务器的组合。

在Spring Boot不需要XML配置(部署描述符)。它使用约定优于配置软件设计范例,这意味着可以减少开发人员的工作量。

我们可以使用Spring STS IDE 或 Spring Initializr 进行开发Spring Boot Java应用程序。

入门程序

需求:基于SpringBoot开发一个Web应用,浏览器发送一个请求/hello之后,给浏览器返回一个字符串"Hello Xxx"。

步骤:

1:创建springboot工程,并勾选web开发相关依赖

2:定义HelloController类,添加方法hello,并添加注解。

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//启动类/引导类
@SpringBootApplication
public class SpringbootWebQuickstartApplication {

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

}

首先是创建这个springboot模块后自带的启动类。

package com.itheima;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // 当前类是一个请求处理类
public class HelloController {
    @RequestMapping("/hello") // 请求路径
    public String hello(String name){
        System.out.println("hello " + name);
        return "Hello " + name + " ~";
    }
}

然后我们要自己写一个请求处理类。

运行成功的结果,从上往下第三行可以看到8080(http)就是我们要在浏览器中输入的URL地址

HTTP协议

概念:Hyper Text Transfer Protocol, 超文本传输协议,规定了浏览器和服务器之间的传输规则。

特点:

1:基于TCP协议:面向连接,安全

2:基于请求--响应模型的:一次请求对应一次响应

3:HTTP协议是无状态的协议:对于事物处理没有记忆能力。每次请求--响应都是独立的。

缺点:多次请求间不能共享数据。

优点:速度快

请求协议:

请求数据格式

请求方式-GET:请求参数在运行中,没有请求体,如:/brand/findAll?name=OPPO&status=1.GET请求大小在浏览器中是有限制的。

请求方式-POST:请求参数在请求体中,POST请求大小是没有限制的。

Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行了封装(HttpServletRequest),在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷

package com.itheima;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RequestController {
    @RequestMapping("/request")
    public String request(HttpServletRequest request){
        //1:获取请求方式
        String method = request.getMethod(); // GET
        System.out.println("请求方式:" + method);

        //2:获取请求url地址
        String url = request.getRequestURL().toString();  // http://localhost:8080/request
        System.out.println("请求url:" + url);

        //3:获取请求协议
        String protocol = request.getProtocol(); // HTTP/1.1
        System.out.println("请求协议:" + protocol);

        //4:获取请求参数 -name, age
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        System.out.println("请求参数:" + name + " " + age);

        //5:获取请求头 -Accept
        String accept = request.getHeader("Accept"); // text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*
        System.out.println("请求头:" + accept);

        return "OK";
    }
}

响应协议

响应数据格式

重点要知道的3个状态码

200:客户端请求成功

404:请求资源不存在,URL资源输入有误,或者网站资源被删除了

500:服务器发生不可预期的错误

Web服务器对HTTP协议的响应数据进行了封装(HttpServletResponse),并在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷

package com.itheima;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
public class ResponseController {

    /**
     方式一:HttpServletResponse设置响应数据
     */
    @RequestMapping("/response")
    public void response(HttpServletResponse response) throws IOException {
        //1:设置响应状态码
        response.setStatus(200);

        //2:设置响应头
        response.setHeader("name", "itheima");

        //3:设置响应体
        response.getWriter().write("<h1>hello response</h1>");
    }
    /**
     方式二:ResponseEntity设置响应数据 -Spring中提供的方式
     */
    @RequestMapping("/response2")
    public ResponseEntity<String> response2() {
        return ResponseEntity.status(200)
                .header("name", "itheima")
                .body("<h1>hello response</h1>");
    }
}

注意:响应状态码 和 响应头如果没有特护要求的话,通常不手动设定。服务器会根据请求处理的逻辑,自动设置状态码和响应头。

案例

1:准备工作:

创建一个SpringBoot工程,并勾选web依赖,lombook

引入资料中准备好的用户数据文件(user.txt),及前端静态页面

定义一个实体类,用来封装用户信息

2:开发服务端程序,接收请求,读取文本数据并响应

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
@Data // 使用lombok依赖,自动生成getter和setter方法
@AllArgsConstructor // 使用lombok依赖,自动生成全参构造方法
@NoArgsConstructor // 使用lombok依赖,自动生成无参构造方法

public class User {

    private Integer id;
    private String username;
    private String password;
    private String name;
    private Integer age;
    private LocalDateTime updateTime; // 保证和前端页面中的属性保持一致,不能乱写
}
package com.itheima.controller;

import cn.hutool.core.io.IoUtil;
import com.itheima.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@RestController
public class UserController {

    @RequestMapping("/list")
    public List<User> list() {

        //1:加载并读取user.txt文件,并获取用户数据
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());

        //2:解析用户信息,封装为User对象 -> list集合
        List<User> usersList = lines.stream().map(line -> {
            String[] split = line.split(",");
            Integer id = Integer.parseInt(split[0]);
            String username = split[1];
            String password = split[2];
            String name = split[3];
            Integer age = Integer.parseInt(split[4]);
            LocalDateTime updateTime = LocalDateTime.parse(split[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).toList();

        //3:返回数据(json)
        return usersList;
    }
}

可以发现响应体返回的是json格式的数据,那么这是什么原因呢?

原因就是@RestController这个注解的底层封装了一个注解:@ReponseBody

这个注解的作用的就是将Controller的返回值直接作为响应体的数据直接响应给前端,如果说返回值是对象或者集合,会先转json再返回

分层解耦

因为所有的功能都在一个方法里,所以上述项目的代码有两个问题:复用性差和难以维护。

我们可以通过分层解耦来解决这两个问题。

三层架构

controller:控制层,接收前端所发出的请求,对请求进行处理并响应数据。

service:业务逻辑层,处理具体的业务逻辑。

dao:数据访问层(Data Access Object) (持久层),负责数据的访问操作,包括数据的增删改查。

这里命名的时候要注意规范,接口的实现类我们一般命名成 XxxxImpl。

我们首先完成Dao层面的接口和接口实现类

package com.itheima.dao;

import java.util.List;

public interface UserDao {
    /**
     * 加载用户数据
     */
    public List<String> findAll();
}
package com.itheima.dao.impl;

import cn.hutool.core.io.IoUtil;
import com.itheima.dao.UserDao;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class UserDaoImpl implements UserDao {
    @Override
    public List<String> findAll() {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
        return lines;
    }
}

我们再完成Service层面的接口和接口实现类

package com.itheima.service;

import com.itheima.pojo.User;

import java.util.List;

public interface UserService {
    /*
    * 查询所有用户信息
    * */
    public List<User> findAll();
}
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import com.itheima.pojo.User;
import com.itheima.service.UserService;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

public class UserServiceImpl implements UserService {

    //首先要从Dao层调取数据
    private UserDao userDao = new UserDaoImpl(); // 这里是多态
    @Override
    public List<User> findAll() {
            List<String> lines = userDao.findAll();
            List<User> usersList = lines.stream().map(line -> {
            String[] split = line.split(",");
            Integer id = Integer.parseInt(split[0]);
            String username = split[1];
            String password = split[2];
            String name = split[3];
            Integer age = Integer.parseInt(split[4]);
            LocalDateTime updateTime = LocalDateTime.parse(split[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).toList();
            return usersList;
    }
}

最后是controller层返回

@RestController
public class UserController {

    private UserService userService = new UserServiceImpl();
    @RequestMapping("/list")
    public List<User> list() {
        //首先调用UserService方法,获取数据
        List<User> usersList = userService.findAll();

        //最后返回数据
        return usersList;
    }
}

最后可以看一下整个流程还是比较清晰的。

IOC & DI(入门)

为了遵守软件设计的原则:高内聚低耦合。

控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。

Bean对象:IOC容器中创建,管理的对象,称之为Bean。

1:将Dao 及 Service层的实现类,交给IOC容器管理。

2:为Controller 及 Service注入运行时所依赖的对象

首先是Dao层

package com.itheima.dao.impl;

import cn.hutool.core.io.IoUtil;
import com.itheima.dao.UserDao;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Component // 将当前组件添加到Spring容器中
public class UserDaoImpl implements UserDao {
    @Override
    public List<String> findAll() {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
        return lines;
    }
}

然后是Service层

package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

@Component
public class UserServiceImpl implements UserService {

    @Autowired //应用程序时,会自动查询该类型的bean对象,并赋值给该成员变量;
    //首先要从Dao层调取数据
    private UserDao userDao;
    @Override
    public List<User> findAll() {
            List<String> lines = userDao.findAll();
            List<User> usersList = lines.stream().map(line -> {
            String[] split = line.split(",");
            Integer id = Integer.parseInt(split[0]);
            String username = split[1];
            String password = split[2];
            String name = split[3];
            Integer age = Integer.parseInt(split[4]);
            LocalDateTime updateTime = LocalDateTime.parse(split[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).toList();
            return usersList;
    }
}

最后是controller层

@RestController
public class UserController {

    @Autowired
    private UserService userService;
    @RequestMapping("/list")
    public List<User> list() {
        //首先调用UserService方法,获取数据
        List<User> usersList = userService.findAll();

        //最后返回数据
        return usersList;
    }
}

IOC详解

要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

如果我们不指定Bean对象的名字,它就默认是当前类名且第一个字母小写

当然我们也可以指定类名

@Repository("userDao")

前面声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描。

该注解虽然没有显示配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication中,默认扫描的范围是启动类所在包及其子包

DI详解

三种注入方式及其优缺点

    方式1:属性注入
    @Autowired
    private UserService userService;

优点:代码简洁,方便快速开发

缺点:隐藏了类之间的依赖关系,可能会破坏类的封装性。

    方式2:构造器注入
    private final UserService userService;
    @Autowired(如果当前类中只有这么一个构造函数,@Autowired可以省略)
    public UserController(UserService userService) {
        this.userService = userService;
    }

优点:能清晰地看到类的依赖关系,提高代码的安全性

缺点:代码繁琐,如果构造参数过多,可能导致构造函数臃肿。

//    方式3:setter方法注入
    private UserService userService;
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

优点:保持了类的封装性。依赖关系更清晰。

缺点:需要额外编写setter方法,增加了代码量

最后一个问题:如果一个接口有多个实现类,即存在多个相同类型的bean,那么我怎么拿到我需要的那个bean呢?

可以用这三个方案来解决问题,@Primary是写在Service层的,代表默认先用那个bean,其他两个都是写在Controller层的。

@Resource 与 @Autowired区别:

 * @Autowired是Spring框架提供的注解,而@Resource是JavaEE规范提供的。

* @Autowired是默认按照类型注入,而@Resource默认是按照名称注入。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值