作业:
-
使用swagger测试controller的api接口
-
使用前后端交互输入图书isbn,显示图书的详细信息。
-
博客内容: 总结单例模式的几种实现方式(可以扩展)
使用swagger测试controller的api接口
- 它是最好的接口测试工具之一
- knife4j–swagger的升级版
引入依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
配置文件
在application.yml中配置
knife4j:
enable: true
对网页请求返回的包装进行注解
对results包下的HttpResp进行注解
@ApiModel("API返回对象")
@Data
@AllArgsConstructor
public class HttpResp<T> {
private RespCode respCode;
@ApiModelProperty(name="results",notes = "反馈结果集")
private T results;
@ApiModelProperty(name="time",notes = "返回时间")
private Date time;
public static <T> HttpResp<T> resp(RespCode respCode, T results, Date time){
return new HttpResp<>(respCode,results,time);
}
}
-
@ApiModel
对装载Http响应的类进行注解 -
@ApiModelProperty
对属性进行注解@ApiModelProperty(name="results",notes = "反馈结果集")
- name:参数名
- notes:参数说明
对controller层下的BookController
进行注解
- 以getAllBooks方法为例
@Api(tags = "图书API接口类")
@RestController
@RequestMapping("/api/book")
public class BookController {
@Autowired
private IBookService bookService;
@ApiOperation(value = "getAllBooks",notes = "查询所有图书")
@ApiImplicitParams({
@ApiImplicitParam(name = "currentPage",required = true),
@ApiImplicitParam(name = "pageSize",required = true)
})
@GetMapping("/getAllBooks")
public HttpResp<PageDTO<BookVo>> getAllBooks(Integer currentPage, Integer pageSize) {
PageDTO<BookVo> books = bookService.findAllBooksByPage(currentPage, pageSize);
return HttpResp.resp(RespCode.BOOK_OPERATION_SUCCESS, books, new Date());
}
-
@Api(tags = "图书API接口类")
-
@ApiOperation(value = "getAllBooks",notes = "查询所有图书")
- 对方法进行注解
- value:方法名
- notes:接口描述
-
@ApiImplicitParams({ @ApiImplicitParam(name = "currentPage",required = true), @ApiImplicitParam(name = "pageSize",required = true) })
-
@ApiImplicitParam:对方法参数进行注解
- name:参数名
* value:参数解释 - required:参数是否必须
* dataType:参数类型
- name:参数名
-
使用前后端交互输入图书isbn,显示图书的详细信息
总结单例模式的几种实现方式
单例模式
单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。Spring就是使用的单例方式进行容器的管理。
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
本质: 借助编程语言自身的语法特性,强制限制某个类,不能创建多个实例。
优点:内存中只有一个实例,减少内存开销;避免对资源多重占用;设置全局访问点,严格控制访问。
缺点:没有接口,扩展困难;如果要扩展单例对象,只有修改代码,没有其他途径,不符合程序的开闭原则。
构建方式
通常单例模式在Java语言中,有两种构建方式:
- 懒汉方式。指全局的单例实例在第一次被使用时构建。
- 饿汉方式。指全局的单例实例在类装载时构建。
懒汉方式
- 为什么要使用
final
?- 防止反射破坏单例
- 为什么说懒汉模式有线程安全的缺陷?
- 单例模式在
多线程
的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
- 单例模式在
- 在Java中,懒汉方式生成单例的实例代码如下:(
Synchronized
的方式)
public class Singleton {
private static volatile Singleton INSTANCE = null;
//私有化构造方法
private Singleton() {};
//使用线程安全的方式
public static Singleton getInstance() {
if(INSTANCE == null){
synchronized(Singleton.class){
//当两个或以上的线程同时在第一时间检测到实例为空时
//为了避免多线程的多次实例化,此时需要加上同步锁
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
ReentrantLock
的方式
public class Singleton {
//私有化构造方法
private Singleton() {};
private static volatile Singleton INSTANCE = null;
private static final ReentrantLock lock = new ReentrantLock();
public static Singleton getInstance() {
try {
lock.lock();//对new步骤进行上锁,比synchronized更灵活,可以自定义解锁的时长,避免上时间上锁对性能产生的影响
if (Objects.isNull(INSTANCE)) {
INSTANCE = new Singleton();
}
} finally {
lock.unlock();//最后解锁
}
return INSTANCE;
}
}
饿汉方式
public class Singleton {
//用static修饰,这样就可以让instance变为这个类的唯一实例
private static final Singleton INSTANCE = new Singleton();
//为了防止Single在类外可以被实例,这边将其的构造方法设计为private。
private Singleton() {};
因为要在不创建额外实例的情况下调用这个方法,必须将其设置为static方法
public static Singleton getInstance() {
return INSTANCE;
}
}
缺点:可能会造成内存空间的浪费
-
为什么说饿汉模式天生就是线程安全的?
-
instance为什么一定要是static的?
-
通过静态的类方法(getInstance) 获取instance,该方法是静态方法,instance由该方法返回(被该方法使用),如果instance非静态,无法被getInstance调用;
-
instance需要在调用getInstance时候被初始化,只有static的成员才能在没有创建对象时进行初始化。且类的静态成员在类第一次被使用时初始化后就不会再被初始化,保证了单例;
-
static类型的instance存在静态存储区,每次调用时,都指向的同一个对象。其实存放在静态区中的是引用,而不是对象。而对象是存放在堆中的。
-
-
单例模式的构造方法为什么私有?
- 设置private以后,每次new对象的时候都要调用构造方法。而private的权限是当前类,那么其他类new对象的时候一定会失败。
- 设置成private是考虑封装性,防止在外部类中进行初始化,也就不是单例了。
枚举方式
-
枚举单例如何限制实例个数:创建时有几个就有几个
-
枚举单例属于懒汉式还是饿汉式:饿汉式,相当于静态成员变量
-
枚举单例能否防止反射:不能。
-
枚举单例能否防止反序列化:可以!枚举父类实现了序列化接口,但是它在反序列化的时作了处理。可以防止反序列化创建新对象
-
枚举单例在单例创建时希望加入初始化逻辑怎么做:可以在构造方法中写逻辑
-
枚举单例在创建时是否有并发问题:没有,在类加载时创建,由jvm保证。不会有并发问题
-
枚举类如何实现线程安全:
通过反编译后,可以看到单例枚举类是一个final类,且创建该对象的实例是在一个static静态语句块中进行的,根据JVM的类加载机制,静态语句块只会在类被加载时执行一次,所以可以线程安全。另外因为单例枚举类反编译后实际上是一个被final修饰的类,所以他不能被继承,也就不能创建子类对象。 -
实现代码
-
public enum SingleEnum { INSTANCE; public void doSomething() { } }
-
-
经过反编译后
public final class EnumSingleton extends Enum< EnumSingleton> { public static final EnumSingleton ENUMSINGLETON; private static final Singleton ENUM$VALUES[]; public static EnumSingleton[] values(); public static EnumSingleton valueOf(String s); static { ENUM$VALUES = (new EnumSingleton[] { ENUMSINGLETON }); };
}
参考文献:
.net/qq_42803467/article/details/119314946 “单例模式——饿汉模式&&懒汉模式”