关于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依赖(初始化项目时不能勾选,否则会出现编译异常)
org.projectlombok lombok true
首先在新建的项目包下新建一个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。 然后调用里面的工具类,就可以非常方便快捷的完成业务操作。
cn.hutool hutool-all 5.8.27
在项目包下创建一个子包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 list(){ //1.加载并读取数据 获取用户信息 //IO流获取文件信息 // InputStream in = new FileInputStream(new File("user.txt")); InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt"); //定义一个User类型的ArrayList存放用户数据 ArrayList lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList()); //2.解析数据,封装成对象 ->集合 //定义一个User属性的List集合 userList //stream() 将lines对集合转化为流对象 //map() 方法将lines集合中的每个元素应用一个函数,然后将结果映射为User对象 List 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 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 findAll() { //1.加载并读取数据 获取用户信息 //IO流获取文件信息 // InputStream in = new FileInputStream(new File("user.txt")); InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt"); //定义一个User类型的ArrayList存放用户数据 ArrayList 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 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 findall() { //1.调用Dao获取数据 List lines = userDao.findAll(); //2.解析数据,封装成对象 ->集合 //定义一个User属性的List集合 userList //stream() 将lines对集合转化为流对象 //map() 方法将lines集合中的每个元素应用一个函数,然后将结果映射为User对象 List 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 list(){ List 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 findAll() { InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt"); ArrayList 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 findAll() { List lines = userDao.findAll(); List 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注入的实现类。
-
-
-