使用 Spring 框架构建 MVC 应用程序:初学者教程

Spring Framework 是一个功能强大、功能丰富且设计精良的 Java 平台框架。它提供了一系列编程和配置模型,旨在简化和精简 Java 中健壮且可测试的应用程序的开发过程。

人们常说 Java 太复杂了,构建简单的应用程序需要很长时间。尽管如此,Java 提供了一个稳定的平台,周围有一个非常成熟的生态系统,这使其成为开发强大软件的绝佳选择。

Spring Framework 是 Java 生态系统中众多强大的框架之一,它附带了一系列编程和配置模型,旨在简化 Java 中高性能和可测试应用程序的开发。

在本教程中,我们将接受构建一个简单的应用程序的挑战,该应用程序将SpringMVC轻松掌握。

Spring Framework 教程入门

要构建基于 Spring 的应用程序,我们需要使用以下构建工具之一:

  • Maven 相关
  • Gradle 相关

在 Spring Tool Suite 中,我们通过从"File > New"菜单下选择"Spring Starter Project"来创建一个新项目。

创建新项目后,我们需要编辑 Maven 配置文件 "pom.xml" 并添加以下依赖项:

xml 复制代码
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-commons</artifactId>
</dependency>

这些列出的依赖项将加载 Spring Boot WebThymeleafJPA H2(将用作我们的内存数据库)。所有必要的库都将自动拉取。

实体类

了能够存储有关开发人员及其技能的信息,我们需要定义两个实体类:"Developer "和"Skill"。

这两个类都被定义为带有一些注解的普通 Java 类。通过在类之前添加@Entity,我们可以将它们的实例提供给 JPA。这将使在需要时从持久性数据存储中存储和检索实例变得更加容易。此外,@Id@GeneratedValue注释允许我们指示实体的唯一 ID 字段,并在存储在数据库中时自动生成其值。

由于开发人员可以拥有许多技能,因此我们可以使用 "@ManyToMany" 注解定义一个简单的多对多关系。

Developer 开发 人员
java 复制代码
@Entity
public class Developer {

	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private long id;
	private String firstName;
	private String lastName;
	private String email;
	@ManyToMany
	private List<Skill> skills;

	public Developer() {
		super();
	}

	public Developer(String firstName, String lastName, String email,
			List<Skill> skills) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = email;
		this.skills = skills;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public List<Skill> getSkills() {
		return skills;
	}

	public void setSkills(List<Skill> skills) {
		this.skills = skills;
	}

	public boolean hasSkill(Skill skill) {
		for (Skill containedSkill: getSkills()) {
			if (containedSkill.getId() == skill.getId()) {
				return true;
			}
		}
		return false;
	}

}
Skill 技能
JAVA 复制代码
@Entity
public class Skill {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id;
    private String label;
    private String description;

    public Skill() {
		super();
    }

    public Skill(String label, String description) {
		super();
		this.label = label;
		this.description = description;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getLabel() {
		return label;
	}

	public void setLabel(String label) {
		this.label = label;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}
    
}

Repositories 存储库

用 JPA,我们可以定义一个非常有用的 Developer Repository 接口和 SkillRepository 接口,它们允许简单的 CRUD 操作。这些接口将允许我们通过简单的方法调用访问存储的开发人员和技能,例如:

  • respository.findAll():返回所有开发人员
  • repository.findOne(id):返回具有给定 ID 的开发人员

要创建这些接口,我们需要做的就是扩展 CrudRepository 接口。

Developer Repository 开发人员存储库
JAVA 复制代码
public interface DeveloperRepository extends CrudRepository<Developer, Long> {

}
Skill Repository 技能仓库
java 复制代码
public interface SkillRepository extends CrudRepository<Skill, Long> {
	public List<Skill> findByLabel(String label);
}

此处声明的附加方法 findByLabel 的功能将由 JPA 自动提供。

Controller 控制器

接下来,我们可以为这个应用程序开发控制器。控制器将映射请求 URI 以查看模板,并在两者之间执行所有必要的处理。

java 复制代码
@Controller
public class DevelopersController {

	@Autowired
	DeveloperRepository repository;

	@Autowired
	SkillRepository skillRepository;

	@RequestMapping("/developer/{id}")
	public String developer(@PathVariable Long id, Model model) {
		model.addAttribute("developer", repository.findOne(id));
		model.addAttribute("skills", skillRepository.findAll());
		return "developer";
	}

	@RequestMapping(value="/developers",method=RequestMethod.GET)
	public String developersList(Model model) {
		model.addAttribute("developers", repository.findAll());
		return "developers";
	}

	@RequestMapping(value="/developers",method=RequestMethod.POST)
	public String developersAdd(@RequestParam String email, 
						@RequestParam String firstName, @RequestParam String lastName, Model model) {
		Developer newDeveloper = new Developer();
		newDeveloper.setEmail(email);
		newDeveloper.setFirstName(firstName);
		newDeveloper.setLastName(lastName);
		repository.save(newDeveloper);

		model.addAttribute("developer", newDeveloper);
		model.addAttribute("skills", skillRepository.findAll());
		return "redirect:/developer/" + newDeveloper.getId();
	}

	@RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST)
	public String developersAddSkill(@PathVariable Long id, @RequestParam Long skillId, Model model) {
		Skill skill = skillRepository.findOne(skillId);
		Developer developer = repository.findOne(id);

		if (developer != null) {
			if (!developer.hasSkill(skill)) {
				developer.getSkills().add(skill);
			}
			repository.save(developer);
			model.addAttribute("developer", repository.findOne(id));
			model.addAttribute("skills", skillRepository.findAll());
			return "redirect:/developer/" + developer.getId();
		}

		model.addAttribute("developers", repository.findAll());
		return "redirect:/developers";
	}

}

URI 到方法的映射是通过简单的 @RequestMapping 注解完成的。在这种情况下,控制器的每个方法都映射到一个 URI。

这些方法的 model 参数允许将数据传递到视图。从本质上讲,这些是键到值的简单映射。

每个控制器方法要么返回要用作视图的 Thymeleaf 模板的名称,要么返回要重定向到的特定模式(redirect:)的 URL。例如,方法 developer_developersList_ 返回模板的名称,而 developersAdddevelopersAddSkill 返回要重定向到的 URL。

在控制器中,@Autowired注释会自动在相应字段中分配我们定义的存储库的有效实例。这允许从控制器内部访问相关数据,而无需处理大量样板代码。

Views 视图

最后,我们需要为要生成的视图定义一些模板。为此,我们使用了 Thymeleaf,一个简单的模板引擎。我们在控制器方法中使用的模型可以直接在模板中使用,即当我们在模型的 contract" 键中输入合约时,我们将能够从模板中以 "contract.name" 的形式访问 name 字段。

Thymeleaf 包含一些控制 HTML 生成的特殊元素和属性。他们非常直观和直接。例如,要使用技能名称填充 span 元素的内容,您只需定义以下属性(假设在模型中定义了键"skill"):

xml 复制代码
<span th:text="${skill.label}"></span>

与设置锚点元素的 href 属性类似,可以使用特殊属性 *th:href

在我们的应用程序中,我们需要两个简单的模板。为清楚起见,我们将在嵌入式模板代码中跳过所有 style 和 class 属性(即 Bootstrap 属性)。

Developer List 开发者名单
html 复制代码
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head> 
	<title>Developers database</title> 
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
	<h1>Developers</h1>
	<table>
		<tr>
			<th>Name</th>
			<th>Skills</th>
			<th></th>
		</tr>
		<tr th:each="developer : ${developers}">
			<td th:text="${developer.firstName + ' ' + developer.lastName}"></td>
			<td>
				<span th:each="skill,iterStat : ${developer.skills}">
					<span th:text="${skill.label}"/><th:block th:if="${!iterStat.last}">,</th:block>
				</span>
			</td>
			<td>
				<a th:href="@{/developer/{id}(id=${developer.id})}">view</a>
			</td>
		</tr>
	</table>
	<hr/>
	<form th:action="@{/developers}" method="post" enctype="multipart/form-data">
		<div>
			First name: <input name="firstName" />
		</div>
		<div>
			Last name: <input name="lastName" />
		</div>
		<div>
			Email: <input name="email" />
		</div>
		<div>
			<input type="submit" value="Create developer" name="button"/>
		</div>
	</form>
</body>
</html>
Developer Details 开发者详情
html 复制代码
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	<title>Developer</title>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
	<h1>Developer</h1>
	Name: <b th:text="${developer.firstName}" /> <b th:text="${developer.lastName}" /><br/>
	Email: <span th:text="${developer.email}" /><br/>
	Skills:
		<span th:each="skill : ${developer.skills}">
			<br/>&nbsp;&nbsp;<span th:text="${skill.label}" /> - <span th:text="${skill.description}" />
		</span>
	<form th:action="@{/developer/{id}/skills(id=${developer.id})}" method="post" enctype="multipart/form-data" >
		<select name="skillId">
			<option th:each="skill : ${skills}" 
				th:value="${skill.id}" 
				th:text="${skill.description}">Skill</option>
		</select>
		<input type="submit" value="Add skill"/>
	</form>
</body>
</html>

启动服务器

Spring 包含一个 boot 模块。这允许我们轻松地从命令行作为命令行 Java 应用程序启动服务器:

java 复制代码
@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    DeveloperRepository developerRepository;

    @Autowired
    SkillRepository skillRepository;

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

}

由于我们使用的是内存中数据库,因此在启动时使用一些预定义数据引导数据库是有意义的。这样,当服务器启动并运行时,数据库中至少会有一些数据。

java 复制代码
@Override
public void run(String... args) throws Exception {
	Skill javascript = new Skill("javascript", "Javascript language skill");
	Skill ruby = new Skill("ruby", "Ruby language skill");
	Skill emberjs = new Skill("emberjs", "Emberjs framework");
	Skill angularjs = new Skill("angularjs", "Angularjs framework");

	skillRepository.save(javascript);
	skillRepository.save(ruby);
	skillRepository.save(emberjs);
	skillRepository.save(angularjs);

	List<Developer> developers = new LinkedList<Developer>();
	developers.add(new Developer("John", "Smith", "john.smith@example.com", 
			Arrays.asList(new Skill[] { javascript, ruby })));
	developers.add(new Developer("Mark", "Johnson", "mjohnson@example.com", 
			Arrays.asList(new Skill[] { emberjs, ruby })));
	developers.add(new Developer("Michael", "Williams", "michael.williams@example.com", 
			Arrays.asList(new Skill[] { angularjs, ruby })));
	developers.add(new Developer("Fred", "Miller", "f.miller@example.com", 
			Arrays.asList(new Skill[] { emberjs, angularjs, javascript })));
	developers.add(new Developer("Bob", "Brown", "brown@example.com", 
			Arrays.asList(new Skill[] { emberjs })));
	developerRepository.save(developers);
}

总结

Spring 是一个多功能框架,允许构建 MVC 应用程序。使用 Spring 构建一个简单的应用程序既快速又透明。该应用程序还可以使用 JPA 轻松与数据库集成。

GitHub 该篇文章中使用到Demo源码整个项目的源代码。

推荐阅读

1、在 Spring 中使用 @EhCache 注解作为缓存
2、有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
3、如何理解应用 Java 多线程与并发编程?
4、Java Spring 中常用的 @PostConstruct 注解使用总结
5、线程 vs 虚拟线程:深入理解及区别
6、深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
7、10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
8、"打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!"
9、Java 中消除 If-else 技巧总结
10、线程池的核心参数配置(仅供参考)
11【人工智能】聊聊Transformer,深度学习的一股清流(13)

相关推荐
Algorithm15762 分钟前
JVM是什么,与Java的关系是什么,以及JVM怎么实现的跨平台性
java·开发语言·jvm
遇见你真好。1 小时前
SpringBoot整合quartz定时任务
java·springboot·quartz
一颗甜苞谷1 小时前
开源一款基于 JAVA 的仓库管理系统,支持三方物流和厂内物流,包含 PDA 和 WEB 端的源码
java·开发语言·开源
攻心的子乐1 小时前
idea使用svn
java·ide·intellij-idea
程序员大佬超1 小时前
IDEA解决 properties 文件乱码问题
java·ide·intellij-idea
秋恬意1 小时前
LinkedList 源码分析
java·开发语言·面试
隔窗听雨眠1 小时前
深入理解Redis的四种模式
java·redis·mybatis
lexusv8ls600h1 小时前
微服务设计模式 - 重试模式(Retry Pattern)
java·spring boot·微服务
Viktor_Ye2 小时前
高效数据集成:易仓退款订单与金蝶销售退货单的自动化对接
java·运维·自动化
feilieren2 小时前
leetcode - 684. 冗余连接
java·开发语言·算法