关于SpringBootWeb的入门学习

一、关于Spring

        Spring的官网(https://spring.io)

        Spring提供了很多开源项目,通过Spring Boot可以帮我们快速的构建应用程序。

        Spring主要是为我们简化配置,快速开发。

二、Spring入门项目

        需求:基于SpringBoot的方式开发一个web应用,浏览器发起请求/hello后,给浏览器返回字符串 “Hello Spring  xxxx”

        在左侧选择Spring Boot生成源,右侧会为我们自动生成相应配置,我们需要注意的是:

        1.IDEA自动为我们配置了Spring的服务器地址(若是网络不好,可用阿里云提供的脚手架:https://start.aliyun.com

        2.开发语言我们选择Java

        3.构建工具要选择Maven(项目中要导入许多相关jar包作为依赖)

        4.JDK版本必须是17及以上

        5.打包方式设置为Jar即可

        我们是面向网页做项目,所以最基础的依赖要勾选Spring Web

        Spring Boot版本号要选择正式版

        

        在com.itheima包下创建一个HelloSpring类

        运行自动创建的引导类(标识有@SpringBootApplication注解的类)

        打开浏览器,输入 http://localhost:8080/hello?name=张三

三、SpringBootWeb案例1

        需求:基于SpringBoot开发web程序,完成用户列表的渲染展示

        准备工作:将user.txt文档以及网页前端内容都放在resources目录下 

        

        在pom.xml文件中导入lombok依赖(初始化项目时不能勾选,否则会出现编译异常)

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

        首先在新建的项目包下新建一个pojo包,专门存放实体类,在pojo下新建一个User类,类中属性要与user.txt中属性名相同,避免后期对接属性产生不必要的麻烦。

package org.itheima.pojo;

import java.time.LocalDateTime;

/**
 * @Author: Jaymr
 * @Date: 2025/5/2 12:54
 * @Version: v1.0.0
 * @Description: 封装用户信息
 **/
@Data//getter setter等方法
@AllArgsConstructor//有参构造
@NoArgsConstructor//无参构造
public class User {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Integer age;
    private LocalDateTime updateTime;
}

        由于在案例中,需要读取文本中的数据,并且还需要将对象转为json格式,所以这里呢,我们在项目中再引入一个非常常用的工具包hutool。 然后调用里面的工具类,就可以非常方便快捷的完成业务操作。

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.27</version>
</dependency>

        在项目包下创建一个子包controller,在其中创建一个UserController类

package org.itheima.controller;

import cn.hutool.core.io.IoUtil;
import org.itheima.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.FileInputStream;
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;

/**
 * @Author: Jaymr
 * @Date: 2025/5/2 13:08
 * @Version: v1.0.0
 * @Description: 完成用户列表的渲染展示
 **/
@RestController//标识为请求处理类
public class UserController {
    @RequestMapping("/list")
    public List<User> list(){
        //1.加载并读取数据 获取用户信息

        //IO流获取文件信息
        // InputStream in = new FileInputStream(new File("user.txt"));
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        //定义一个User类型的ArrayList存放用户数据
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());

        //2.解析数据,封装成对象 ->集合
        //定义一个User属性的List集合 userList
        //stream() 将lines对集合转化为流对象
        //map() 方法将lines集合中的每个元素应用一个函数,然后将结果映射为User对象
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");//按照,拆分字符串 拆分的字符串存放到part数组内
            Integer id = Integer.parseInt(parts[0]);//Integer.parseInt() 方法将字符串转换为整数
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        // collect() 将流处理后的结果收集起来
        // Collectors.toList() 告诉程序把所有处理后的元素放进userList中

        //3.响应数据
        /**
         * 底层会直接将list集合转换为json格式(@RestController注解底层用到@ResponseBody注解
         * 将Controller返回值直接作为响应体的数据直接响应;
         * 返回值是对象/集合,则将其转为json再作为响应数据返回)
         */
        return userList;
    }
}

        启动服务测试,在浏览器中访问:http://localhost:8080/user.html

四、分层解耦

        为了解决后期代码维护时的便捷性,以及提高代码的可复用性,增强代码的可读性,我们可以把一个项目分为三层架构(单一职责原则)。

        以以上项目为例,大体可分为三个部分:

  • 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。(Dao)

  • 逻辑处理:负责业务逻辑处理的代码。(Service)

  • 请求处理、响应数据:负责,接收页面的请求,给页面响应数据。(Controller)

        所以我们对上述案例以三层架构的模式进行优化:

        在项目包下创建三个包:dao,service,controller

        dao包下新建一个接口UserDao以规范Dao层代码

package org.itheima.dao;

import org.itheima.pojo.User;

import java.util.List;

/**
 * @Author: Jaymr
 * @Date: 2025/5/2 13:56
 * @Version: v1.0.0
 **/
public interface UserDao {

    public List<User> findAll();
}

        在dao包下新建一个包Impl,里面存放实现类,在Impl包里新建UserDaoImpl类实现UserDao接口:

package org.itheima.dao.Impl;

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

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

/**
 * @Author: Jaymr
 * @Date: 2025/5/2 13:59
 * @Version: v1.0.0
 * @Description: TODO
 **/
public class UserDaoImpl implements UserDao {
    @Override
    public List<String> findAll() {
        //1.加载并读取数据 获取用户信息

        //IO流获取文件信息
        // InputStream in = new FileInputStream(new File("user.txt"));
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        //定义一个User类型的ArrayList存放用户数据
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
        return lines;//ArrayList 是 List 接口的一个实现类 多态
    }
}

        在service包下进行同样的操作:

        创建UserService接口:

package org.itheima.service;

import org.itheima.pojo.User;

import java.util.List;

/**
 * @Author: Jaymr
 * @Date: 2025/5/2 14:06
 * @Version: v1.0.0
 **/
public interface UserService {
    public List<User> findall();
}

         创建在service包下新建一个包Impl,里面存放实现类,在Impl包里新建UserServiceImpl类实现UserService接口:

package org.itheima.service.Impl;

import org.itheima.dao.Impl.UserDaoImpl;
import org.itheima.dao.UserDao;
import org.itheima.pojo.User;
import org.itheima.service.UserService;

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

/**
 * @Author: Jaymr
 * @Date: 2025/5/2 14:08
 * @Version: v1.0.0
 * @Description: TODO
 **/
public class UserServiceImpl implements UserService {
    //创建userDao对象
    private UserDao userDao = new UserDaoImpl();
    @Override
    public List<User> findall() {
        //1.调用Dao获取数据
        List<String> lines = userDao.findAll();
        //2.解析数据,封装成对象 ->集合
        //定义一个User属性的List集合 userList
        //stream() 将lines对集合转化为流对象
        //map() 方法将lines集合中的每个元素应用一个函数,然后将结果映射为User对象
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");//按照,拆分字符串 拆分的字符串存放到part数组内
            Integer id = Integer.parseInt(parts[0]);//Integer.parseInt() 方法将字符串转换为整数
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        // collect() 将流处理后的结果收集起来
        // Collectors.toList() 告诉程序把所有处理后的元素放进userList中
        return userList;
    }
}

        controller包中,仅需一个UserController类,因为直接与服务器对接,无需创建接口。

package org.itheima.controller;

import cn.hutool.core.io.IoUtil;
import org.itheima.pojo.User;
import org.itheima.service.Impl.UserServiceImpl;
import org.itheima.service.UserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.FileInputStream;
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;

/**
 * @Author: Jaymr
 * @Date: 2025/5/2 13:08
 * @Version: v1.0.0
 * @Description: TODO
 **/
@RestController//标识为请求处理类
public class UserController {
    private UserService userService = new UserServiceImpl();
    @RequestMapping("/list")
    public List<User> list(){
        List<User> userList = userService.findall();
        //3.响应数据
        /**
         * 底层会直接将list集合转换为json格式(@RestController注解底层用到@ResponseBody注解
         * 将Controller返回值直接作为响应体的数据直接响应;
         * 返回值是对象/集合,则将其转为json再作为响应数据返回)
         */
        return userList;
    }
}

       启动服务测试,在浏览器中访问:http://localhost:8080/user.html,结果与原案例相同。

        进一步解耦

        我们注意到,每次需要一个对象,我们都要new一个相应的对象。如果我们日后要更换实现类,就需要修改调用原实现类类中的代码。(如:我们现在弃用了UserDaoImpl,使用UserDaoImpl2,那么就需要在UserServiceImpl中修改new的对象)。

        解耦思路:

        1.首先就不能new一个具体的对象。但是不new对象,就意味着没有上一层对象。

        那么我们可以提供一个容器,容器中存储相应对象(如:UserDaoImpl,UserDaoImpl2)

        然后UserServiceImpl直接从容器中获取对应对象

        这里,Spring提供了两个核心概念:(专有名词和解释不看也罢,看我的解释)

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

    • 对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。

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

    • 程序运行时需要某个资源,此时容器就为其提供这个资源。

    • 例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。

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

        简单来说,就是你想喝水,但是你杯子里没有水,你只想用自己的杯子喝水。那就需要别人把水倒在你的杯子里,你去用自己的杯子喝水,你并不关心水是哪来的,只要有水喝就就行。

        用自己的杯子喝水 → 控制反转(IOC)

        别人把水倒进杯子 → 依赖注入( DI )

        我自己的杯子就是DI,那个容器就是IOC

IOC/DI的核心是——通过反转控制权,将依赖关系的管理从组件内部转移到外部容器

        那我们怎么实现呢?

        IOC/DI实现

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

        在实现类上加@Component,就代表把当前类的对象交给IOC

        如:UserDaoImpl类

@Component
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;
    }
}

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

        使用@Autowired注解

        如:UserServiceImpl类

        

@Component
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    
    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        return userList;
    }
}

        启动测试服务,在服务器中访问:http://localhost:8080/user.html,效果同上。

五、IOC详解

        之前把对象交给IOC容器时,我们用到@Component注解,但具体是哪一层我们看不出,所以Spring给@Component加了几个衍生注解:

注解

说明

位置

@Component

声明bean的基础注解

不属于以下三类时,用此注解

@Controller

@Component的衍生注解

标注在控制层类上

@Service

@Component的衍生注解

标注在业务层类上

@Repository

@Component的衍生注解

标注在数据访问层类上(由于与mybatis整合,用的少)

六、DI详解  

        在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。

        @Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)

        1.构造器注入(推荐):通过在类的构造方法上使用@Autowired注解,Spring会自动将匹配的Bean注入到构造器的参数中,从而实现Bean的自动装配。

@Component
public class UserService {
    private UserDao userDao;

    @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}

        2.属性注入(代码简洁,但原理是利用暴力反射,破坏代码的封装性):通过在类的属性上使用@Autowired注解,Spring会自动将匹配的Bean注入到属性中,从而实现Bean的自动装配。

@Component
public class UserService {
    @Autowired
    private UserDao userDao;
}

        3.方法注入:通过在类的方法上使用@Autowired注解,Spring会自动将匹配的Bean注入到方法的参数中,从而实现Bean的自动装配。

@Component
public class UserService {
    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

        注意事项: 

        如果同时准备了两个UserService的实现类并且都交给了IOC容器管理,通常情况下会导致Spring无法确定要注入哪个实现类,从而出现冲突。

        为了解决这个问题,可以使用@Primary@Qualifier@Resource三种注解来指定要注入的实现类。

        1.Primary注解:可以标注在一个实现类上,告诉Spring优先选择注入这个实现类

@Component
@Primary
public class UserServiceImpl1 implements UserService {
    // 实现类1的代码
}

@Component
public class UserServiceImpl2 implements UserService {
    // 实现类2的代码
}

        2.Qualifier注解:可以和@Autowired注解一起使用,指定具体要注入的实现类

@Autowired
@Qualifier("serviceImpl1")
private UserService userService;
@Component("serviceImpl1")
public class UserServiceImpl1 implements UserService {
    // 实现类1的代码
}

@Component("serviceImpl2")
public class UserServiceImpl2 implements UserService {
    // 实现类2的代码
}

        3.@Resource注解:可以通过指定的name属性来指定要注入的实现类

    @Resource(name = "serviceImpl1")
    private UserService userService;
    
    @Component("serviceImpl1")
    public class UserServiceImpl1 implements UserService {
        // 实现类1的代码
    }
    
    @Component("serviceImpl2")
    public class UserServiceImpl2 implements UserService {
        // 实现类2的代码
    }
    

    通过以上三种方式,可以解决同一接口有多个实现类时的冲突问题,并指定Spring注入的实现类。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值