Spring Boot 之小白入门
一、HelloWorld Web
在Idea上创建基于Spring Boot的web 项目,当客户端浏览器访问该web资源时,返回的网页显示 “helloword Spring Boot!这是一个用Spring Boot开发的网站。”
(一)新建Spring Boot Web项目
1.选择Spring Initializr快速创建spring boot项目;
2.第一次创建该项目时需要下载jar包,所以需要更换maven下载源为阿里云;
3.选择项目名称与路径。
4.创建完成后项目结构如下所示:
(二)helloworld代码编写
1.在HelloworldApplication同级目录下创建新package命名为controller
2.在controller包下新建类名为hellocontroller
3.创建完成后如下所示:
4.hellocontroller中代码如下:
package com.example.helloworld.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class hellocontroller {
@GetMapping("/hello")
@ResponseBody
public String Hello() {
return "helloword Spring Boot!这是一个用Spring Boot开发的网站。";
}
}
(三)运行结果
1.点击运行后可见Tomcat已启动
2.此时在浏览器输入http://localhost:8080/hello/hello
二、RESTful 接口的 Web服务
(一)初步了解HTTP协议
掌握B/S之间的请求Request、服务响应Response和get、put、post等主要概念和技术方法
参考:
第二章 RESTful接口实现与测试
HTTP基础知识
(二)在Idea上创建基于Spring Boot的web 项目
当客户端浏览器分别用get、put、post等访问该web服务资源时,服务器能做出正确的结果
1.新建工程并导入spring web依赖
2.新建项目结构如下:
3.编码:
演示的功能就是提供一个计数器功能,可以初始化计数器,修改计数器,查询计数器当前值。没有使用数据库,直接用一个单例类来模拟了
Count
package com.example.projects1.bean;
public class Count {
private int count;
public int getCount(){
return count;
}
public void setCount(int count){
this.count = count;
}
}
ResourceController
package com.example.projects1.controller;
import com.example.projects1.bean.Count;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.example.projects1.service.ResourceService;
@RestController
public class ResourceController {
@Autowired
ResourceService resourceService;
@RequestMapping(value = "/example/count", method = RequestMethod.PUT)
@ResponseBody
public void initCount(@RequestBody Count count){
resourceService.initCount(count);
}
@RequestMapping(value = "/example/count", method = RequestMethod.POST)
@ResponseBody
public void modifyCount(@RequestBody Count count){
resourceService.addCount(count);
}
@RequestMapping(value = "/example/count", method = RequestMethod.GET)
@ResponseBody
public Count getCount()
{
return resourceService.getCount();
}
}
ResourceManager
package com.example.projects1.manager;
public class ResourceManager {
private int count = 0;
private static ResourceManager instance = new ResourceManager();
private ResourceManager(){}
public static ResourceManager getInstance(){
return instance;
}
public synchronized void addCount(int i){
count = count + i;
}
public synchronized void minusCount(int i){
count = count -i;
}
public int getCount(){
return count;
}
public void initCount(int i){
count = i;
}
}
ResourceService
package com.example.projects1.service;
import com.example.projects1.bean.Count;
import com.example.projects1.manager.ResourceManager;
import org.springframework.stereotype.Service;
@Service
public class ResourceService {
public void addCount(Count count){
if (count != null){
ResourceManager.getInstance().addCount(count.getCount());
}
}
public void minusCount(Count count){
if (count != null) {
ResourceManager.getInstance().minusCount(count.getCount());
}
}
public Count getCount() {
Count count = new Count();
count.setCount(ResourceManager.getInstance().getCount());
return count;
}
public void initCount(Count count){
if (count != null) {
ResourceManager.getInstance().initCount(count.getCount());
}
}
}
4.在Projects1Application类中右键运行
服务器正常启动
(三)用Postman对你的web进行测试
安装Postman 软件,用Postman对你的web进行测试
1.下载并安装Postman
Postman界面
2.测试
服务提供了三个接口:
URL是:http://localhost:8080/example/count
只是分PUT、POST和GET,其中PUT用于初始化,POST用于修改(这里修改是累加),GET用于查询。
(1)新建测试界面
(2)查询接口
(3)初始化:
(4)再次查询接口
(5)修改接口
(7)修改后查询
三、软件设计模式的学习(以Java为例)
掌握“单例模型”和“工厂模式”(又分为简单工厂模型、工厂方法模型、抽象工厂模式)的概念和优缺点。调通例子代码。
(一)单例模式
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
1.单例模式的实现方法
常见的实现方式有:懒汉模式、饥汉模式、双重校验锁、静态内部类、枚举等
(1)懒汉模式
/**
* @author hz
* @version 1.0
*/
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
//如果还没有被实例化过,就实例化一个,然后返回
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
这段代码里,我们没有考虑线程安全,所以可能还是会参数多个实例,所以我们需要对线程安全问题进行解决,从而得出懒汉模式的优化版
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance(){
//如果还没有被实例化过,就实例化一个,然后返回
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
通过关键字synchronized用于确保getInstance方法线程安全,但是这种方式弊端特别大,因为所有线程到达该方法以后需要进行排队等候,所以对性能的损耗非常大,该处理解决了线程安全安全问题,并没有解决效率问题,如果要既要解决线程安全问题又要解决效率问题则需要我们后面的双重校验锁方式。
懒汉模式将实例化的时机放到了需要使用的时候(饿汉是类加载了就有实例),也就是“延迟加载”,相比饿汉,能避免了在加载的时候实例化有可能用不到的实例,但是问题也很明显,我们要花精力去解决线程安全的问题。
(2)饿汉模式
public class Singleton {
//类加载的时候instance就已经指向了一个实例
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
饿汉模式相比懒汉模式,在类加载的时候就已经存在一个实例,举个例子,比如数据库连接吧,懒汉就是第一次访问数据库的时候我才去创建一个连接,而饿汉呢,是你程序启动了,类加载好了的时候,我已经有个连接了,你用不用不一定了,所以饿汉的缺点也就出来了:可能会产生很多无用的实例。
那么加载时机的问题我们已经说过了,接下来就是线程安全了,代码里我们并没有看见synchronized关键字,那么这种方式是如何确保线程安全的呢,这个就是JVM类加载的特性了,JVM在加载类的时候,是单线程的,所以可以保证只存在单一的实例
(3)双重校验锁
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
首先要说明的是,双重检验锁也是一种延迟加载,并且较好的解决了在确保线程安全的时候效率低下的问题,对比一下最原始的那种线程安全的方法(就是懒汉模式的第二种代码),那种方法将整个getInstance方法锁住,那么每次调用那个方法都要获得锁,释放锁,等待等等…而双重校验锁锁住了部分的代码。进入方法如果检查为空才进入同步代码块,这样很明显效率高了很多。
(4)静态内部类
public class Singleton {
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
懒汉模式需要考虑线程安全,所以我们多写了好多的代码,饿汉模式利用了类加载的特性为我们省去了线程安全的考虑,那么,既能享受类加载确保线程安全带来的便利,又能延迟加载的方式,就是静态内部类。
Java静态内部类的特性是:加载的时候不会加载内部静态类,使用的时候才会进行加载。而使用到的时候类加载又是线程安全的,这就完美的达到了我们的预期效果。
(5)枚举
public enum Singleton {
INSTANCE;
}
JDK1.5提供了一个新的数据类型:枚举。
枚举的出现提供了一个较为优雅的方式取代以前大量的static final类型的变量。而这里,我们也利用枚举的特性,实现了单例模式,外部调用由原来的Singleton.getInstance变成了Singleton.INSTANCE了。
以上几种单例模式的实现各自有各自的优势,所以我们在实际使用中,需要根据自己的需求进行选择,而通过上述实现方式我们可以可以总结一下单例模式的特点:
- 单例模式只有一个实例对象;
- 该单例对象必须由单例类创建;
- 单例类对外提供一个访问该单例的全局访问点。
2.单例模式的优缺点
(1)优点
- 单例模式可以保证内存里只有一个实例,减少了内存的开销;
- 可以避免对资源的多重占用;
- 单例模式设置全局访问点,可以优化和共享资源的访问。
(2)缺点
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则;
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象;
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
3.单例模式的应用场景
对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web
- 中的配置对象、数据库的连接池等。
(二)工厂模式
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。所有我们在实际开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。而按实际业务场景划分,工厂模式有 3 种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。
1.简单工厂模式
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。
在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。
(1)简单工厂模式的组成
- 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
- 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
- 具体产品(ConcreteProduct):是简单工厂模式的创建目标。
(2)简单工厂模式的实现方法
public class Client {
//抽象产品
public interface Product {
void show();
}
//具体产品:ProductA
static class ConcreteProduct1 implements Product {
public void show() {
System.out.println("具体产品1显示...");
}
}
//具体产品:ProductB
static class ConcreteProduct2 implements Product {
public void show() {
System.out.println("具体产品2显示...");
}
}
final class Const {
static final int PRODUCT_A = 0;
static final int PRODUCT_B = 1;
static final int PRODUCT_C = 2;
}
static class SimpleFactory {
public static Product makeProduct(int kind) {
switch (kind) {
case Const.PRODUCT_A:
return new ConcreteProduct1();
case Const.PRODUCT_B:
return new ConcreteProduct2();
}
return null;
}
}
}
(3)简单工厂模式优缺点
优点:
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
- 客户端无需知道所创建具体产品的类名,只需知道参数即可。
- 可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点
- 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
- 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
- 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
但是简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”,因此我们进行进一步对简单工厂进行抽象,这个也是工厂模式的第二个实现场景,工厂方法模式。
2.工厂方法模式
前面我们说了简单工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
(1)工厂方法模式的主要角色
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
对比简单工厂模式,工厂方法模式在之间简单的工厂的基础之上将工厂进行拆分成抽象工厂和具体工厂两部分内容,这样灵活性得到大量提升。
(2)工厂方法模式的使用场景
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
(3)工厂方法模式的实现方法
public class AbstractFactoryTest {
public static void main(String[] args) {
try {
Product a;
AbstractFactory af;
af = (AbstractFactory) ReadXML.getObject();
//抽象工厂内容放入到外部配置文件xml/properties等文件中,通过I/O流加载从而创建出抽象工厂
a = af.newProduct();
a.show();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
//抽象产品:提供了产品的接口
interface Product {
public void show();
}
//具体产品1:实现抽象产品中的抽象方法
class ConcreteProduct1 implements Product {
public void show() {
System.out.println("具体产品1显示...");
}
}
//具体产品2:实现抽象产品中的抽象方法
class ConcreteProduct2 implements Product {
public void show() {
System.out.println("具体产品2显示...");
}
}
//抽象工厂:提供了厂品的生成方法
interface AbstractFactory {
public Product newProduct();
}
//具体工厂1:实现了厂品的生成方法
class ConcreteFactory1 implements AbstractFactory {
public Product newProduct() {
System.out.println("具体工厂1生成-->具体产品1...");
return new ConcreteProduct1();
}
}
//具体工厂2:实现了厂品的生成方法
class ConcreteFactory2 implements AbstractFactory {
public Product newProduct() {
System.out.println("具体工厂2生成-->具体产品2...");
return new ConcreteProduct2();
}
}
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
public class ReadXML {
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
public static Object getObject() {
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src/FactoryMethod/config1.xml"));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = "FactoryMethod." + classNode.getNodeValue();
//System.out.println("新类名:"+cName);
//通过类名生成实例对象并将其返回
Class<?> c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
(4)工厂方法模式的优缺点
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点:
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
3.抽象工厂模式
抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构,这里便出现了两个概念:同族和同等级。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品
(1)抽象工厂模式的主要角色
作为工厂方法模式的升级版,抽象工厂模式的组成同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
(2)抽象工厂模式的使用条件
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
(3)抽象工厂模式的实现
public class FarmTest {
public static void main(String[] args) {
try {
Farm f;
Animal a;
Plant p;
//读取相应的配置信息,用于生产工厂
f = (Farm) ReadXML.getObject();
a = f.newAnimal();
p = f.newPlant();
a.show();
p.show();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
//抽象产品:动物类
interface Animal {
public void show();
}
//具体产品:马类
class Horse implements Animal {
public Horse() {
System.out.println("具体马类的生成");
}
public void show() {
System.out.println("执行马类的相应操作");
}
}
//具体产品:牛类
class Cattle implements Animal {
public Cattle() {
//具体牛类的生成
System.out.println("具体牛类的生成");
}
public void show() {
System.out.println("执行马类的相应操作");
}
}
//抽象产品:植物类
interface Plant {
public void show();
}
//具体产品:水果类
class Fruitage implements Plant {
public Fruitage() {
System.out.println("具体水果类生成");
}
public void show() {
System.out.println("执行水果类的相应操作");
}
}
//具体产品:蔬菜类
class Vegetables implements Plant {
public Vegetables() {
System.out.println("具体蔬菜类生成");
}
public void show() {
System.out.println("执行蔬菜类的相应操作");
}
}
//抽象工厂:农场类
interface Farm {
public Animal newAnimal();
public Plant newPlant();
}
//具体工厂:农场类1
class SGfarm implements Farm {
public Animal newAnimal() {
System.out.println("新牛出生!");
return new Cattle();
}
public Plant newPlant() {
System.out.println("蔬菜长成!");
return new Vegetables();
}
}
//具体工厂:农场类2
class SRfarm implements Farm {
public Animal newAnimal() {
System.out.println("新马出生!");
return new Horse();
}
public Plant newPlant() {
System.out.println("水果长成!");
return new Fruitage();
}
}
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.File;
public class ReadXML2 {
public static Object getObject() {
try {
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src/AbstractFactory/config.xml"));
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = "AbstractFactory." + classNode.getNodeValue();
System.out.println("新类名:" + cName);
Class<?> c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
(4)抽象工厂模式的优缺点
优点:
- 抽象工厂模式除了工厂方法模式的优点都具有以外,还具有以下独特优点
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
- 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
(5)抽象工厂模式的适用场景
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
参考文献:
1.Springboot Web:从零开始写helloworld
2.第二章 RESTful接口实现与测试
3.HTTP基础知识
4.【SpringBoot】使用IDEA创建一个SpringBoot服务,并创建三个restful风格的接口
5.设计模式-单例模式
6.JAVA 设计模式系列——工厂模式
7.设计模式系列——抽象工厂模式