lengjingbuliaoyidian
文章15
标签7
分类7

结构型模式

  • 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者则采用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

  • 结构型模式分为一下7种:
  1. 代理模式
  2. 适配器模式
  3. 装饰者模式
  4. 桥接模式
  5. 外观模式
  6. 组合模式
  7. 享元模式

代理模式(中介商模式)

  • 概述:
    代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

Java中的代理按照代理类生成实际不同又分为静态代理和动态代理。静态代理类在编译时期就生成,而动态代理类则是在Java运行时动态生成,动态代理又有JDK代理和CGLib代理。

  • 结构:
    代理模式分为三种角色:
  1. 抽象主题类(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题类(RealSubject):实现抽象主题类中的具体业务,是代理对象所代表的真是对象,是最终要引用的对象。
  3. 代理类(Proxy):提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
  • 优缺点:

    1. 优点:
      • 代理模式在客户端与目标对象之间起到了一个中介作用和保护目标对象的作用
      • 代理对象可以扩展目标对象的功能
      • 代理模式能将客户端与目标对象分离,在一定程度上减低了系统的耦合性
    2. 缺点:
      • 代理模式会导致系统中类的数量增加,在一定程度上增加了系统的复杂度
      • 代理模式的性能可能会受到影响,因为代理对象需要额外的处理来转发请求
  • 使用场景:

  1. 远程代理
    2.防火墙代理
  2. 保护代理

静态代理

  • 实例:火车站售票
    • 场景:
      • 火车站售票系统中,售票窗口是客户端,而售票系统是服务器端。为了保护售票系统的安全,我们可以使用代理模式来实现。
    • 代码:
      • 抽象主题类(Subject):
        public interface SellTickets {
        void sell();
        }
      • 真实主题类(RealSubject):
        public class TrainStation implements SellTickets {
        @Override
        public void sell() {
        System.out.println("火车站售票");
        }
        }
      • 代理类(Proxy):
        public class TrainStationProxy implements SellTickets {
        //先声明对象
        private TraninStation traninStation = new TraninStation();
        @Override
        public void sell() {
        System.out.println("代理点收取一些服务费用");
        traninStation.sell();
        }
        }
      • 客户端:
        public class Client {

        //代理模式
        public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
        }
        }


        //结果:代理点收取一些服务费用 火车站卖票

动态代理——JDK代理模式

  • Java中提供了一个动态代理类Proxy,Proxy并不是上面我们上述所说的代理对象类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法),来获取代理对象。

  • 实例:同样是上面的卖票案例,这次我们使用JDK动态代理模式来实现

  • 抽象主题类(Subject):

    public interface SellTickets {
    void sell();
    }

  • 真实主题类(RealSubject):

public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站售票");
}
}
  • 代理主题类(ProxyFactory):
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//获取代理对象的工厂类——代理类也实现了对应的接口
public class ProxyFactory {
//先声明一个目标对象
private TraninStation station = new TraninStation();

public SellTickets getProxyObject(){
//通过newProxyInstance返回代理对象(创建对象是接口形式的需要转化一下)
/**
* ClassLoader loader,类加载器,用于加载代理类,可以通过目标对象获取类加载器
*Class<?>[] interfaces,代理类实现的接口的字节码对象
*InvocationHandler h),代理对象的调用处理程序
*/
SellTickets proxyObject= (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/**
* Object proxy,代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不需要使用
* Method method,对接口中的方法进行封装的method对象
* Object[] args,调用方法的实际参数
*/

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//System.out.println("invoke方法执行了");
System.out.println("代售点收取手续费");
//执行对象的方法
Object obj= method.invoke(station, args);
return obj;
}

}
);
return proxyObject;
}
}
  • 测试类:

public class Client {
public static void main(String[] args) {
//获取代理对象
//1.创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//2.通过代理工厂对象获取代理对象
SellTickets proxyObject = factory.getProxyObject();
//3.调用方法
proxyObject.sell();
}
}
  • 注意:在’ProxyFactory’类中,getProxyObject()方法返回的是SellTickets接口,而不是TrainStation类。
    这是因为JDK动态代理模式是基于接口的代理,而不是基于类的代理。
    所以,代理对象只能实现与目标对象相同的接口,而不能继承目标对象的类。
    同时代理类是程序在运行过程中动态的在内存中生成的类。

  • 上述的执行流程:

    1. 在测试类中通过代理对象调用sell()方法
    2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法,而不是TrainStation类中的sell()方法。
    3. 在$Proxy0类中,sell()方法的实现是通过InvocationHandler接口的子实现类对象的invoke()方法来完成的。
    4. 在invoke()方法中,通过反射机制调用执行真实所属类(TraninStation)目标对象中的sell()方法。

动态代理——CGLIB代理模式

  • CGLIB(Code Generation Library)是一个基于ASM的字节码生成类库(代码生成包,是第三方提供的包,所以需要时引入jar包),用于在运行时动态生成一个类。
  • 在pom.xml文件中引入CGLIB的依赖:
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
    public class TraninStation {

public void sell() {
System.out.println("火车站卖票");
}
}
    import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* @Description: 代理对象工厂,用来获取代理对象(目标对象所属的子类)
* @
* @
*/
public class ProxyFactory implements MethodInterceptor {
//声明火车站对象
private TraninStation traninStation = new TraninStation();
public TraninStation getProxyObject(){
//创建Enhancer对象,类似于JDK动态代理中的Proxy类
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TraninStation.class);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TraninStation proxyObject = (TraninStation) enhancer.create();
return proxyObject;
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//System.out.println("代理对象拦截方法");
System.out.println("代理对象拦截方法,执行目标对象的方法之前");
System.out.println("代售点收取一定的服务费用CGLIB");
//执行目标对象的方法
Object invoke = method.invoke(traninStation, objects);
return invoke;
}
}
    public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TraninStation proxyObject =factory.getProxyObject();
//通过代理对象调用方法
proxyObject.sell();
}
//结果是:
//代理对象拦截方法,执行目标对象的方法之前
//代售点收取一定的服务费用CGLIB
//火车站卖票
}

总结:

jdk动态代理基于反射实现,cglib基于继承实现,所以cglib动态代理类是目标类的的子类,而jdk动态代理类是目标对象实现的接口的子类。

建造者模式

建造者模式

建造者模式(Builder Pattern)

  • 概述:主要目的是将一个复杂对象的构建过程与其表示相分离,从而可以创建具有不同表示形式的对象。(使得同样的构造过程可以创建不同的表示)

结构:

建造者模式包含以下几个主要角色:

  1. 抽象建造者(Builder):规定了实现复杂对象的哪些部分的创建,并不设计具体的部件对象的创建。

  2. 具体建造者(Concrete Builder):实现抽象建造者接口,完成复杂产品的各个部件的具体创建方法。在构建过程完成之后,提供产品的实例。

  3. 产品(Product):要构建的复杂对象。产品类通常包含多个部分或属性。

  4. 指导者(Director):负责调用具体建造者的方法来构建产品,指导者并不了解具体的构建过程,只关心产品的构建顺序和方式

-Bike产品.java

public class Bike{
private String frame;
private String seat;

public String getFrame(){
return frame;
}
public void setFrame(String frame){
this.frame=frame;
}
public String getSeat(){
return seat;
}
public void setSeat(String seat){
this.seat=seat;
}
}
  • Builder抽象建造者.java

    public abstract class Builder{

    protected Bike bike=new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();

    //返回建造产品

    public abstract Bike createBike();

    }
  • 具体建造者1.java

        public class MobikeBuider extends Builder {
    @Override
    public void builerFrame() {
    bike.setFrame("摩拜碳钎维车架");
    }

    @Override
    public void builerSeat() {
    bike.setSeat("摩拜真皮车座");

    }

    @Override
    public Bike createBike() {
    return bike;
    }
    }
  • 具体建造者2.java

    public class OfoBuilder extends Builder{
    @Override
    public void builerFrame() {
    bike.setFrame("Ofo铝合金车架");
    }

    @Override
    public void builerSeat() {
    bike.setSeat("Ofo橡皮车座");

    }

    @Override
    public Bike createBike() {
    return bike;
    }
    }
  • 指导者(Director).java

    private Builder builder;

    public Direactor(Builder builder){
    this.builder=builder;
    }
    public Bike construct(){
    builder.buildFrame();
    builder.buildSeat();
    return builder.createBike();

    }
  • 总结:

  1. 指挥者用于指导具体构建者如何构建产品,控制调用的先后顺序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,就可以把指挥者类和抽象建造者进行合并。
   public  abstract class Builder {
/*
Builder 构造器相当于一个构建成品的骨架蓝图
*/
//声明Bike类型的变量
protected Bike bike = new Bike();

public abstract void builerFrame();

public abstract void builerSeat();

public abstract Bike createBike();

public Bike build(){
this.builerFrame();
this.builerSeat();
return this.createBike();
}
}
  1. 这样做确实简化了系统结构,但是同时家中了抽象建造者类的职责,也不太符合单一职责原则,如果construct过于复杂,建议还是封装到Director中。

使用场景:

  • 需要生成的对象具有复杂的内部结构。
  • 需要生成的对象内部属性相互依赖。

优点

分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示。
可以更好地控制构建过程,隐藏具体构建细节。
代码复用性高,可以在不同的构建过程中重复使用相同的建造者。

缺点

如果产品的属性较少,建造者模式可能会导致代码冗余。
增加了系统的类和对象数量。

原型模式

原型模式

原型模式(Prototype Pattern)

  • 概述:
    原型模式(Prototype Pattern)是创建型模式,用于当创建对象的成本高时,通过复制一个现有对象来创建一个新对象,从而避免创建一个新对象的 。
    这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
  • 结构:
    • 抽象原型类:定义了一个克隆自身的接口,即规定了具体原型对象必须实现的clone()方法。
    • 具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象。
    • 客户端类:使用具体原型类中的clone()方法来克隆新的对象。
  • 实现:
    原型模式的克隆分为:浅克隆和深克隆。
  1. 浅克隆创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,任指向原有属性所指向的对象的内存地址。
  2. 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不会指向原有对象地址。
  • 注意:Java中的Object类提供了clone()方法,该方法返回一个对象,该对象是当前对象的一个副本。
    • 浅克隆:调用Object类的clone()方法。
    • 深克隆:
      1. 实现Cloneable接口。
      2. 重写clone()方法。
      3. 在clone()方法中,使用super.clone()方法创建一个新对象,然后将当前对象的属性值复制到新对象中。
      4. 如果属性是引用类型,需要递归调用clone()方法。

在Java中,原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过新建类实例的方式。在实现原型模式时,我们通常会使用clone()方法,而复制对象时就会涉及到浅克隆(Shallow Clone)和深克隆(Deep Clone)的概念。

浅克隆(Shallow Clone)

浅克隆是指当复制一个对象时,对于基本数据类型的成员变量,会直接复制其值;而对于引用类型的成员变量,则只复制其引用地址(也就是内存地址),而不会复制引用所指向的实际对象。因此,在浅克隆中,原始对象和克隆对象中的引用类型成员变量将指向同一个对象。
在Java中,通过实现Cloneable接口并重写Object类的clone()方法来实现浅克隆。Object类的clone()方法默认实现就是浅克隆。
示例代码:

class Address {
private String city;
public Address(String city) {
this.city = city;
}
// getter and setter
}
class Person implements Cloneable {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// getter and setter
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅克隆
}
}
// 使用
Address address = new Address("Beijing");
Person p1 = new Person("Tom", address);
Person p2 = (Person) p1.clone();
// 此时,p1和p2的address引用指向同一个Address对象
System.out.println(p1.getAddress() == p2.getAddress()); // true

深克隆(Deep Clone)

深克隆是指复制对象时,不仅复制对象本身,而且递归复制对象所引用的其他对象。因此,深克隆会复制整个对象网络,使得原始对象和克隆对象之间互不影响,它们拥有各自独立的内存空间。
实现深克隆的方式有多种:

  1. 重写clone()方法,并在其中对引用类型进行递归克隆(需要引用类型也实现Cloneable接口并重写clone()方法)。
  2. 使用序列化(Serialization)和反序列化(Deserialization)来实现深克隆。将对象写入到字节流中,然后再从字节流中读取回来,这样会创建一个完全独立的副本。
    示例代码(通过重写clone方法实现深克隆):
    class Address implements Cloneable {
    private String city;
    public Address(String city) {
    this.city = city;
    }
    // getter and setter
    @Override
    protected Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }
    class Person implements Cloneable {
    private String name;
    private Address address;
    public Person(String name, Address address) {
    this.name = name;
    this.address = address;
    }
    // getter and setter
    @Override
    protected Object clone() throws CloneNotSupportedException {
    Person cloned = (Person) super.clone();
    // 对引用类型单独克隆
    cloned.address = (Address) address.clone();
    return cloned;
    }
    }
    // 使用
    Address address = new Address("Beijing");
    Person p1 = new Person("Tom", address);
    Person p2 = (Person) p1.clone();
    // 此时,p1和p2的address引用指向不同的Address对象
    System.out.println(p1.getAddress() == p2.getAddress()); // false

序列化实现深克隆

如果引用类型嵌套层次很深,或者引用类型没有实现Cloneable接口,则可以通过序列化的方式实现深克隆。需要确保所有涉及的类都是可序列化的(实现Serializable接口)。
示例代码:

import java.io.*;
class Address implements Serializable {
private String city;
public Address(String city) {
this.city = city;
}
// getter and setter
}
class Person implements Serializable {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// getter and setter
public Person deepClone() throws IOException, ClassNotFoundException {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
}
}
// 使用
Address address = new Address("Beijing");
Person p1 = new Person("Tom", address);
Person p2 = p1.deepClone();
System.out.println(p1.getAddress() == p2.getAddress()); // false

总结

  • 浅克隆:复制基本数据类型的值,对于引用类型,只复制引用地址,因此原对象和克隆对象会共享引用类型的成员变量。修改其中一个对象的引用类型成员变量,另一个对象也会受到影响。
  • 深克隆:复制基本数据类型的值,同时递归复制引用类型的对象。因此,原对象和克隆对象之间完全独立,互不影响。
    在实际应用中,选择浅克隆还是深克隆取决于具体需求。如果对象的引用类型成员变量在创建后不会改变,或者你希望共享这些对象,那么浅克隆就足够了,并且效率更高。如果需要完全独立的副本,则应该使用深克隆。但要注意深克隆可能带来的性能问题,特别是在对象图很大的情况下。

关键区别总结

特性 浅克隆 深克隆
引用类型复制 复制内存地址(共享对象) 递归复制实际对象(完全独立)
修改影响 修改引用类型成员会影响所有副本 修改引用类型成员不影响其他副本
实现复杂度 简单(默认clone() 复杂(需递归处理所有引用类型)
性能 高效(不创建新对象) 较低(递归创建新对象)
适用场景 引用类型不可变或无需隔离时 引用类型需完全隔离时

注意事项

  1. String的特殊性
    虽然String是引用类型,但其不可变性(Immutable)使得浅克隆中修改String值会创建新对象,不会影响原对象(行为类似深克隆)。但其他引用类型(如自定义类)仍需谨慎。
  2. 深克隆的替代方案
    序列化(Serializable)是另一种深克隆实现,但要求所有涉及的对象都实现Serializable接口:
    public static <T> T deepClone(T obj) throws IOException, ClassNotFoundException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(obj);

    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return (T) ois.readObject();
    }
单例模式

单例模式

Singleton(单例模式)

  1. 意图:
    确保一个类只有一个实例,并提供一个全局访问点来访问该实例

  2. 注意事项

  • 线程安全:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成实例被多次创建。
  • 延迟初始化:实例在第一次调用 getInstance() 方法时创建。
  • 序列化和反序列化:重写 readResolve 方法以确保反序列化时不会创建新的实例。
  • 反射攻击:在构造函数中添加防护代码,防止通过反射创建新实例。
  • 类加载器问题:注意复杂类加载环境可能导致的多个实例问题。
  1. 结构
    单例模式包含以下几个主要角色:
  • 单例类:包含单例实例的类,通常将构造函数声明为私有。
  • 静态成员变量:用于存储单例实例的静态成员变量。
  • 获取实例方法:静态方法,用于获取单例实例。
  • 私有构造函数:防止外部直接实例化单例类。
  • 线程安全处理:确保在多线程环境下单例实例的创建是安全的。

单例模式的实现

  1. 单例设计模式分类两种:
  • 饿汉式:类加载就会导致该单例实例对象被创建,适用于单线程环境。
  • 懒汉式:类加载不会导致该单例实例对象被创建,而是首次使用该对象时才会创建,适用于多线程环境。

饿汉式——静态成员变量

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 在本类中创建本类对象
private static Singleton instance =new Singleton();

//3. 提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}

测试类

public calss Client{
public static void main(String[] args) {
//创建Singleton对象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判断获取到的两个对象是否是同一个对象
if(instance==instance1){
System.out.println("两个对象是同一个对象");
}else{
System.out.println("两个对象不是同一个对象");
}
}
}
//结果: 两个对象是同一个对象
//原因: 饿汉式是在类加载时就创建了实例对象,所以在多线程环境下也不会出现多个实例的情况。

饿汉式——静态代码块

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 先申明Singleton对象
private static Singleton instance;//null

//3. 在静态代码块中进行赋值——创建Singleton对象
static{
instance = new Singleton();
}

//3. 提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}

饿汉式——枚举

  • 原由:枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会加载一次。而且枚举类型是所用单例实现中唯一不会被破坏的单例实现模式。
public enum Singleton{
INSTANCE;
}

测试类

 public class Client{
public static void main(String[] args){
//创建Singleton对象
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance==instance1);
}
}
// 结果为true,说明是同一个对象
  • 总结:
    优点: 内存中只有一个实例,减少内存开销,尤其是频繁创建和销毁实例时(如管理学院首页页面缓存)。
    避免资源的多重占用(如写文件操作)。
    缺点:饿汉式在类加载时就创建了实例对象,可能造成内存的浪费。

懒汉式——线程不安全情况

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 先申明Singleton对象
private static Singleton instance;//null

//3. 提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
//首先判断instance是否为null,如果为null,说明还没有创建实例对象,则创建
if(instance==null){
//当有两个线程同时调用getInstance()方法时,线程1等待,线程2获取到了cpu的执行权,也会进入判断里面————》可能会创建多个实例对象,因此需要使用同步锁synchronized --》public static synchronized Singleton getInstance()
instance = new Singleton();
}
//如果instance不为null,则直接返回
return instance;
}
}

测试类


public class Client{
public static void main(String[] args){
//创建Singleton对象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判断获取到的两个对象是否是同一个对象
System.out.println(instance==instance1);
}
}
//结果为ture

懒汉式——线程安全情况

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 先申明Singleton对象
private static Singleton instance;//null

//3. 提供一个公共的访问方式,让外界获取该对象,同时加上synchronized锁保证线程的安全
public static synchronized Singleton getInstance(){
//首先判断instance是否为null,如果为null,说明还没有创建实例对象,则创建
if(instance==null){

instance = new Singleton();
}
//如果instance不为null,则直接返回
return instance;
}
}

懒汉式——双重检查锁

  • 优势:解决了单例、性能、线程安全问题。因为在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重新排序操作。可以使用 volatile 关键字修饰成员变量和静态成员变量,防止指令重排序。
public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2.声明Singleton对象
public static volatile Singleton instance;

//3.提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
//第一次判断,如果instance不为null,则直接返回
if(instance==null){
synchronized (Singleton.class){
//第二次判断,如果instance不为null,则直接返回,为空就创建该对象
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}

懒汉式——静态内部类

  • 原由:静态内部类单里模式由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性、方法被调用时才会被加载,并初始化其静态资源,静态资源由于被static修饰,因此只会被创建一次,并且创建时是线程安全的。
public class Singleton{
//1.私有构造方法
private Singleton(){}
//2.定义一个静态内部类
public static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

测试类


public class Client{
public static void main(String[] args){
//创建Singleton对象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判断获取到的两个对象是否是同一个对象
System.out.println(instance==instance1);
}
}
结果为ture
  • 说明:第一次加载Singleton类时并不会初始化instance,只有第一次调用getInstance()方法时,虚拟机加载SingletonHolder类并初始化instance,并且instance是static的,因此只会被创建一次。这样不仅能保证线程的安全性,还能保证Singleton类的唯一性。

存在的问题——破坏单例模式

破会啊单例模式:使上面定义的单例类可以创建多个对象,美剧方式除外,有两种方式:序列化和反射。
例子:

  1. 序列化与反序列化
public class Singleton implements Serializable{
//1.私有构造方法
private Singleton(){}
//2.定义一个静态内部类
public static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

测试类:

public class Client{
public static void main(String[] args) throws Exception {
//writeObject2File(); //执行结果为:C:\\Users\\j.txt 文件创建成功
readObjectFromFile(); //执行结果为:地址1
readObjectF romFile(); //执行结果为:地址2
//说明:序列化和反序列化后的对象不是同一个对象,因此破坏了单例模式。
}
//从文件中读取数据(对象)
public static void readObjectFromFile() throw Exception{
//1.创建输入流对象
ObjectInputStream ois =new ObjectInputStream(new FileInputStream("C:\\Users\\j.txt"));
//2.读取对象
Singleton instance=(Singleton) ois.readObject();
System.out.println(instance);
//3。释放资源
ois.close();
}
//从文件中写数据(对象)
public static void writeObject2File() throws Exception{
//1.获取Singleton对象
Singleton instance = Singleton.getInstance();
//2.创建输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\j.txt"));
//3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();
}
}
  1. 反射
    public class Client{
    public static void main(String[] args) throws Exception{
    //1.获取Singleton的Class字节码对象
    Class class =Singleton.class;
    //2.获取无参构造方法对象
    Constructor constructor = class.getDeclaredConstructor();
    //3.取消访问检查
    constructor.setAccessible(true);
    //4.通过无参构造方法创建对象
    Singleton instance1 = (Singleton) constructor.newInstance();
    Singleton instance2 = (Singleton) constructor.newInstance();
    //5.判断两个对象是否相等,如果相等,说明反射成功,如果不相等,说明反射失败(破坏了单例模式)
    System.out.println(instance1 == instance2);
    }
    //结果为false
    }

上述问题的解决

1.序列化与反序列化破坏单例模式的解决方法:

  • 在Singleton类中添加readResolve()方法,在序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,反之,就返回新new出来的对象。
    public class Singleton implements Serializable{
    //1.私有构造方法
    private Singleton(){}
    //2.定义一个静态内部类
    public static class SingletonHolder{
    //在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE = new Singleton();
    }
    //3.提供公共的访问方式
    public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
    }
    //4.当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve(){
    return SingletonHolder.INSTANCE;
    }
    }

    测试类:
    public class Client{
    public static void main(String[] args) throws Exception {
    //writeObject2File(); //执行结果为:C:\\Users\\j.txt 文件创建成功
    readObjectFromFile(); //执行结果为:地址1
    readObjectF romFile(); //执行结果为:地址1
    //说明:创建对象时,使用的是同一个对象
    }
    //从文件中读取数据(对象)
    public static void readObjectFromFile() throw Exception{
    //1.创建输入流对象
    ObjectInputStream ois =new ObjectInputStream(new FileInputStream("C:\\Users\\j.txt"));
    //2.读取对象
    Singleton instance=(Singleton) ois.readObject();
    System.out.println(instance);
    //3。释放资源
    ois.close();
    }
    //从文件中写数据(对象)
    public static void writeObject2File() throws Exception{
    //1.获取Singleton对象
    Singleton instance = Singleton.getInstance();
    //2.创建输出流对象
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\j.txt"));
    //3.写对象
    oos.writeObject(instance);
    //4.释放资源
    oos.close();
    }
    }
    2.反射破坏单例模式的解决方法:
    public class Singleton {
    private static boolean flag = false;

    //1.私有构造方法
    private Singleton(){

    synchronized (Singleton.class) {
    //判断flag的值是否为true,
    //如果为true,说明已经创建了对象,直接抛出异常
    //如果为false,说明没有创建对象,创建对象
    if (flag) {
    throw new RuntimeException("不能创建多个对象");
    }
    flag = true;
    }
    }

    //2.定义一个静态内部类
    public static class SingletonHolder{
    //在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE = new Singleton();
    }

    //3.提供公共的访问方式
    public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
    }
    }

    public class Client{
    public static void main(String[] args) throws Exception{
    //1.获取Singleton的Class字节码对象
    Class class =Singleton.class;
    //2.获取无参构造方法对象
    Constructor constructor = class.getDeclaredConstructor();
    //3.取消访问检查
    constructor.setAccessible(true);
    //4.通过无参构造方法创建对象
    Singleton instance1 = (Singleton) constructor.newInstance(); //正常
    Singleton instance2 = (Singleton) constructor.newInstance(); //抛出异常:不能创建多个对象
    //5.判断两个对象是否相等,如果相等,说明反射成功,如果不相等,说明反射失败(破坏了单例模式)
    System.out.println(instance1 == instance2);
    }

    }
工厂模式

工厂模式

工厂模式(Factory Pattern)

在Java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,又假如我们要更换对象,就要将所有new对象的地方都要需改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只要和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂中更换对象即可,达到了与对象解耦的目的。所以说,工厂模式最大的优点是:减低耦合。

工厂模式类型

  1. 简单工厂模式(Simple Factory Pattern):不属于23中设计模式,因为违反了开闭原则

简单工厂模式不是一个正式的设计模式,但它是工厂模式的基础。它使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。

  1. 工厂方法模式(Factory Method Pattern):

工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。

  1. 抽象工厂模式(Abstract Factory Pattern):

抽象工厂模式提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。

简单工厂模式:

  • 结构:
  • 抽象产品:定义了产品的规范,描述了产品的主要特征和功能
  • 具体产品:实现了抽象产品接口,定义了产品的具体功能(实现或者继承了抽象产品的子类)
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品
  • 步骤 1:
    创建一个接口:Shape.java
    public interface Shape{
    void Shape();
    }
  • 步骤 2:
    创建实现接口的实体类。
    Rectangle.java
    public class Restangle implements Shape{
    @Override
    public void Shape(){
    System.out.println("Rectangle");
    }
    }

Square.java

public class Square implements Shape{
@Override
public void Shape(){
System.out.println("Square");
}
}
  • 步骤 3:
    创建一个工厂,生成基于给定信息的实体类的对象。
    ShapeFactory.java
    public calss ShapeFactory{

    //使用getShape()方法获取到形状类型的对象
    public Shape getShape(String shapeType){
    if(shapeTuype==null){
    return null;
    }
    if(shapeType.equalsIgnoreCase("Rectangle")){
    return new Restangle();
    }
    if(shapeType.equalsIgnoreCase("Square")){
    return new Square();
    }

    }
    }
  • 步骤4:
    使用该工厂,通过传递类型信息来获取实体类对象
    FactoryPatternDemo.java
    public class FactoryPatternDemo{
    public static void main(String[] args){
    ShapeFactory shapeFactory =new ShapeFactory();
    //获取矩形对象,并调用绘制方法
    Shape shape1=shapeFactory.getShape("Rectangle");
    shape1.Shape();

    }
    }
    //结果是:Rectangle

工厂方法模式

  • 结构:
    工厂模式主要包含一下几个主要角色:

  • 抽象产品:定义产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了该产品对象的共同方法。

  • 具体产品:实现了抽象产品接口,定义了具体产品的特定行为和属性。

  • 抽象工厂:声明了创建产品的抽象方法,可以是接口或者抽象类,它可以有多个方法用于创建不同类型的产品。

  • 具体工厂:实现了抽象工厂接口,定义了创建具体产品的方法,并返回具体产品对象。

  • 抽象工厂:

    public interface CoffeeFactory{
    //创建Coffee对象的方法
    Coffee createCoffee();

    }
  • 具体工厂:

    public class AmericanCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee(){
    return new AmericanCoffee();
    }

    }
    public class LatteCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee(){
    return new LatteCoffee();
    }
    }
  • 抽象产品:

    public abstract class Coffee{
    // 抽象方法声明
    public abstract String getName();
    public void addMilk(){
    System.out.println("加奶");
    }
    public void addSugar(){
    System.out.println("加糖");
    }

    }
  • 具体产品:

    public class AmericanCoffee extends Coffee{

    public String getName(){
    return "AmericanCoffee";
    }
    }
    public class LatteCoffee extends Coffee{

    public String getName(){
    return "LatteCoffee";
    }
    }

    中间依赖类:

    public class CoffeeStore{

    private CoffeeFactory factory;

    public void SetFactory(CoffeeFactory factory){
    this.factory=factory;
    }
    //实现点Coffee功能
    public Coffee OrderCoffee(){
    Coffee coffee=factory.CreateCoffee();
    coffee.addMilk();
    coffee.addSugar();
    return coffee;
    }

    }

    Client 测试类

    public class Client{
    public static void main(String[] args){
    //创建咖啡店对象
    CoffeeStore store=new CoffeeStore();
    //创建咖啡工厂对象
    CoffeeFactory factory=new AmericanCoffeeFactory();
    //设置咖啡工厂对象
    store.SetFactory(factory);
    //点咖啡
    Coffee coffee=store.OrderCoffee();

    System.out.println(coffee.getName());
    }
    }
    //结果是:加奶 加糖 AmericanCoffee
  • 工厂方法模式优势:

  1. 解耦:客户端与具体产品类解耦,调用者只需要知道对象的名称即可创建对象。

  2. 扩展性:添加新产品只需新增具体工厂和产品类

  3. 单一职责:每个工厂只负责创建一种产品

  4. 开闭原则:对扩展开放,对修改关闭

  • 工厂方法模式缺点:
  1. 每次增加一个产品时,都需要增加一个具体类和对应的工厂,使系统中类的数量成倍增加,增加了系统的复杂度和具体类的依赖。

抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

抽象工厂模式提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体实现类。通过使用抽象工厂模式,可以将客户端与具体产品的创建过程解耦,使得客户端可以通过工厂接口来创建一族产品。

抽象工厂模式通常涉及一族相关的产品,每个具体工厂类负责创建该族中的具体产品。客户端通过使用抽象工厂接口来创建产品对象,而不需要直接使用具体产品的实现类。

  • 结构:
    抽象工厂模式的主要角色如下:

  • 抽象工厂:提供了创建产品的接口,它包含了多个创建产品的方法,可以创建多个不同等级的产品。

  • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。

  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

  • 优点:
    确保同一产品族的对象一起工作。
    客户端不需要知道每个对象的具体类,简化了代码。

  • 缺点:
    扩展产品族非常困难。增加一个新的产品族需要修改抽象工厂和所有具体工厂的代码。

  • 适用场景:
    1.当系统需要创建多个相关或依赖的对象,而不需要指定具体类时。
    2.当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、电冰箱、空调等。
    3.系统中有多个产品族,而系统只消费其中的一族产品。
    4.当一个产品族中的多个对象被设计成一起工作时,需要确保客户端代码不会依赖于具体的产品类。

  • 抽象工厂:

          
    public interface DessertFactory {
    //生产咖啡的功能
    Coffee createCoffee();
    //生产甜品的功能
    Dessert createDessert();
    }
  • 具体工厂:

    //抽象工厂模式 美式风味的甜品工厂:美式咖啡、抹茶慕斯
    public class AmerricanDessertFactory implements DessertFactory {


    @Override
    public Coffee createCoffee() {
    return new AmericanCoffee();
    }

    @Override
    public Dessert createDessert() {
    return new MatchMousse();
    }
    }
//意大利风味甜品工厂:创建意大利风味的咖啡和甜品
public class ItalyDessertFactory implements DessertFactory{

@Override
public Coffee createCoffee() {
return new LatteCoffee();
}

@Override
public Dessert createDessert() {
return new Trimisu();
}
}
  • 抽象产品:
    public abstract class Dessert {
    public abstract void show();
    }
public abstract class Coffee{
// 抽象方法声明
public abstract String getName();
public void addMilk(){
System.out.println("加奶");
}
public void addSugar(){
System.out.println("加糖");
}

}
  • 具体产品:
    public class Trimisu extends Dessert{
    @Override
    public void show() {
    System.out.println("Trimisu");
    }
    }
    public class MatchMousse extends Dessert{
    @Override
    public void show() {
    System.out.println("MatchMousse");
    }
    }
public class AmericanCoffee extends Coffee{

public String getName(){
return "AmericanCoffee";
}
}
public class LatteCoffee extends Coffee{

public String getName(){
return "LatteCoffee";
}
}
  • 测试类:
    public class Client {
    public static void main(String[] args) {
    //创建的是意大利风味甜品工厂对象
    ItalyDessertFactory factory=new ItalyDessertFactory();
    //再从工厂里面获取所需要的产品
    Coffee coffee=factory.createCoffee();
    Dessert dessert=factory.createDessert();

    System.out.println(coffee.getName());
    dessert.show();

    }
    }

    //结果为:LatteCoffee Trimisu

模式扩展:

简单工厂+配置文件接触耦合:
可以通过工厂模式+配置文件的方式可解除工厂对象和产品对象的耦合,在工厂类中加载配置文件的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

  • 第一步:定义配置文件
    创建一个配置文件,将产品类全限定名保存在文件中,文件名任意,如:bean.properties
    <bean.properties>
    coffee=com.liuyue.factorymethod.Coffee
    dessert=com.liuyue.factorymethod.Dessert
    american=com.liuyue.factorymethod.AmericanCoffee
    latte=com.liuyue.factorymethod.LatteCoffee
    match=com.liuyue.factorymethod.MatchMousse
    trimisu=com.liuyue.factorymethod.Trimisu
    <bean.properties>

  • 第二部:改进工厂类:

        public class DessertFactory {
            public static Dessert createDessert(String type){
                try {
                    //加载配置文件
                    Properties properties=new Properties();
                    properties.load(new FileInputStream("bean.properties"));
                    //获取type全限定类名
                    String className=properties.getProperty(type);
                    //通过反射创建对象
                    return (Dessert) Class.forName(className).newInstance();
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }
            public static Coffee createCoffee(String type){
                try {
                    //加载配置文件
                    Properties properties=new Properties();
                    properties.load(new FileInputStream("bean.properties"));
                    //获取type全限定类名
                    String className=properties.getProperty(type);
                    //通过反射创建对象
                    return (Coffee) Class.forName(className).newInstance();
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }
        //测试类
        public class Client {
            public static void main(String[] args) {
                //创建的是意大利风味甜品工厂对象
                ItalyDessertFactory factory=new ItalyDessertFactory();
                //再从工厂里面获取所需要的产品
                Coffee  coffee=factory.createCoffee();
                Dessert dessert=factory.createDessert();

                System.out.println(coffee.getName());
                dessert.show();
                //通过简单工厂+配置文件的方式创建对象
                Coffee coffee1=DessertFactory.createCoffee("latte");
                Dessert dessert1=DessertFactory.createDessert("trimisu");
                System.out.println(coffee1.getName());
                dessert1.show();
            }
        }       

















JavaWeb

JavaWeb

Vue

  1. Vue是一款用于构建用户界面的渐进式的JavaScript框架(基于数据渲染成用户能看到的界面)

  2. 使用 ES 模块构建版本​
    在本文档的其余部分我们使用的主要是 ES 模块语法。现代浏览器大多都已原生支持 ES 模块。因此我们可以像这样通过 CDN 以及原生 ES 模块使用 Vue:

     html
     <div id="app">{{ message }}</div>
    
     <script type="module">
       import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
    
       createApp({
         setup() {
           const message = ref('Hello Vue!')
           return {
             message
           }
         }
       }).mount('#app')
     </script>
    
     注意我们使用了 <script type="module">,且导入的 CDN URL 指向的是 Vue 的 ES 模块构建版本。
    

vue的常用指令

  1. v-for:列表渲染,遍历容器的元素或者对象的属性

         <body>
             <div id="app">
                 <table border="3px" style="padding: 0; background-color: pink; width: 100px; height: 40px;">
                     <tr v-for="(u,index) in users" :key="index">
                         <!--index是指序号-->
                         <td>{{index +1}}</td>
                         <td>{{u.id}}</td>
                         <td>{{u.name}}</td>
                         <td>{{u.gender ===1?'男':'女'}}</td>
                     </tr>
                 </table>
             </div>
             <script type="module">
                 import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
                 createApp({
                     data() {
                         return {
                             users: [
                                 { "id": 1, "name": "张三", "gender": 1 },
                                 { "id": 2, "name": "李四", "gender": 2 },
                                 { "id": 3, "name": "王五", "gender": 1 }
    
                             ]
                         }
    
                     }
                 }).mount('#app');
    
             </script>
    
  2. v-bind: 动态为HTML标签绑定属性值,如设置href css样式等

       注意:动态的为标签的属性绑定值,不能使用差值表达式,得使用v-bind指令。且绑定得数据,必须在data中定义。差值表达式不能出现在标签内部
    
  3. v-if/v-else-if/v-else: 条件性的渲染某元素,判定为true时渲染,否则反之

  4. v-show: 根据条件展示某元素,区别在于切换的是display属性的值

     区别:
          1.v-if="表达式",基于条件判断,来控制创建或移除元素节点(条件渲染)例如<span v-if="gender==1">男生</span> ,适用的场景`不频繁切换得场景`。
          2.v-show="表达式",基于css样式display来控制显示与隐藏,适用的场景`频繁切换显示隐藏得场景`。
    
  5. v-model: 在表单元素上创建双向数据绑定

     <select id="position" name="position" v-model="Searchfrom.job">
     注意:v-model指令用于在表单元素上创建双向数据绑定。
         1. v-model指令用于在表单元素上创建双向数据绑定。
         2. 当表单元素的值发生改变时,绑定的数据也会发生改变。
         3. 当绑定的数据发生改变时,表单元素的值也会发生改变。
    
  6. v-on:为HTML标签绑定事件

       <button type="button" v-on:click="search">查询</button>
       注意:v-on:事件名="方法名",方法名不能加引号,且方法名必须在methods中定义。
    

Ajax

  1. Ajax是一种用于创建快速动态网页的技术,通过在后台与服务器进行少量数据交换,获取服务器响应的数据。Ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
    2.异步交互技术Axios:
    1. 是一个基于Promise的HTTP客户端,用于浏览器和Node.js
    2. Axios对原生态的Ajax进行了封装,提供了更加方便的API
    3. 步骤:
      1.安装axios:npm install axios
      2.引入axios:import axios from ‘axios’
      3.发送请求:axios.get(url)
    4. 官网:https://www.axios-http.cn可查看使用情况

Maven使用

  1. maven的作用:

    依赖管理、项目构建、统一项目结构(插件执行框架)

  2. 创建maven项目

  3. 导入maven项目

    建议将要导入的maven项目直接复制到项目目录下

    建议选择maven项目的pom.xml文件进行导入

  4. 依赖配置的方式例如:

             <dependencies>
    
             <dependency>
    
                     <groupId>org.springframework.boot</groupId>
    
                     <artifactId>spring-boot-starter-test</artifactId>
    
                     <scope>test</scope>
    
                 </dependency>
    
             </dependencies>
    
  5. 移除依赖

             <exclusions>
    
                 <exclusion>
    
                     <groupId>org.springframework.boot</groupId>
    
                         <artifactId>spring-boot-starter-test</artifactId>
    
                 </exclusion>
    
             </exclusions>
    
  6. 生命周期

每套生命周期包含一些阶段,阶段都是有顺序的,后面的阶段依赖于前面的阶段

clean(clean阶段):移除上一次构生成的文件

default(compile:编译项目源代码、test:使用合适的单元测试框架进行测试如junit、package:将编译后的文件打包,如jar\\waR等、install:		安装项目到本地仓库)

site(site阶段)

值得注意的是:在同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行

执行指定周期的方式:
1.在ideal中,右侧的maven工具栏,选中对应的生命周期,双击运行

2.在ideal中,找到对应的项目,右键点击open in进入磁盘,用cmd命令行执行对应的生命周期的阶段

例如:mvn clean
  1. 单元测试Junit:

用法:测试类中的方法的正确性

优点(对于main方法而言):测试代码与应用程序代码分开,便于维护;可以自动生成测试报告;一个测试方法执行失败,不影响其他的测试方法。

前置工作:在pom.xml文件中引入Junit的jar包依赖,然后在相应的项目中的main同级中的test文件中编写junit测试类

  1. 断言:

由于测试方法运行不报错,不代表业务方法逻辑没问题,所以通过断言可以检测方法运行结果是否和预期一致,从而判断业务方法的正确性。

    在测试方法中使用断言的方法:

                            Assertions.assertXxxx()

    就是接口的具体实现类,直接以参数的形式写出来
  1. 依赖范围:

         <dependency>
    
             <groupId>org.springframework.boot</groupId>
    
             <artifactId>spring-boot-starter-test</artifactId>
    
         <scope>test</scope>  指定maven的依赖范围,参见的取值有:compile(默认)、test、provided、runtime**
    
         </dependency>
    
  2. maven常见爆红问题解决:

要是依赖下载不成功就会爆红,在maven本地仓库中生成了xxxx.lastUpdated文件,若是该文件不删除,依赖就不会再下载了

方法:
根据maven依赖的坐标,找到仓库中对于的xxxx.lastUpdated文件,删除,删除之后重新加载项目即可
或者在本地仓库中通过命令行的形式,执行del /s *.lastUpdated 批量删除指定目录下面的xxxx.lastUpdated文件,删除之后重新加载项目即可
注意:要是重新加载依赖,依赖下载之后还是爆红,此时可以关闭IDEA,重新打开IDEA加载项目即可

SpringBoot项目创建

  • SpringBoot的官网:spring.io
  • 如何创建Spring web项目:
    社区版IDEA:
    示例:通过插件创建SpringBoot项目

打开 Settings/Preferences,进入 Plugins。

搜索并安装 Spring Assistant 插件。

重启IDEA后,新建项目时选择 Spring Assistant 标签。

按照提示填写项目信息并选择依赖,完成后即可生成SpringBoot项目。

org.springframework.boot spring-boot-starter-web 方法1:使用Spring官方工具

访问 Spring Initializr 网站,选择语言、依赖等配置后生成项目。下载解压后,用IDEA打开即可。

方法2:手动创建Maven项目

在IDEA中创建一个普通的Maven项目。

修改 pom.xml 文件,添加SpringBoot相关依赖。

配置主类和资源文件,如 application.yml。

@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
通过以上方法,即使在社区版中也可以顺利开发SpringBoot项目。

Web基础——HTTP协议

  1. HTTP协议特点:
    1.基于TCP协议,面向连接,安全
    2.基于请求——响应模型:一次请求对应一次响应
    3.HTTP协议是无状态的协议:对于事务处理没有记忆功能。每次请求——响应都是独立的
    缺点:多次请求间不能共享数据
    优点:速度快

——所以在Web开发中一般使用会话解决上述多次请求间不能共享数据的问题

  1. HTTP协议——请求数据格式:
    请求行:请求方式、资源路径、协议
    请求头:格式key-value
    请求体:(对于POST请求独有的)存放请求的参数

对于两种不同的请求方式来说请求参数的存放位置是不同的,
GET:请求参数在请求行中,可见安全性不高,没有请求体
POST:请求参数在请求体中,不可见安全性高,POST请求大小是没有限制的

  1. HTTP协议数据获取:
    Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行封装到HttpServletRequest,所以可以直接调用Controller方法的时候传递给了该方法。

  2. HTTP响应数据:
    Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行封装到HttpServletRseponse ,所以可以直接调用Controller方法的时候传递给了该方法。

Mybatis入门:

mybatis 属于一种持久层框架,用于简化JDBC的开发。 用于数据访问dao持久层

官网:https://mybatis.net.cn/getting-started.html

数据库连接池:是一个容器,负责分配管理数据库连接(Connection),允许应用程序重复使用一个现有的数据库连接,而不是在重新建立一个。同时释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏。

切换数据库连接池(又spring boot默认的Hikari的数据库连接池转换到Druid):,优势:资源复用,提升系统的响应速度,使用接口DataSource
1.在pom.xml配置文件中,加入配置信息

<dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    2.application.yml配置文件中
spring.datasource.type/url/driver-class-name/username/password

Mybatis中的#号和$号

#{...}:占位符。执行时,会将#{...}替换为?,生成预编译SQL,使用场景为参数值传递,安全性能高:
${...}:拼接符。直接将参数拼接在SQL语句中,存在SQL注入问题,使用场景为表明,字段名动态设置时使用,不安全,性能低

例子:

    @Delete("delete from dept where id=#{id}");
    @Select("selectn id,name,score from ${tableName} order by ${sortField}")    


       
javascript

javascript

JavaScript是什么(如有不懂得可以查看mdn)

  1. 含义:简称js,是一种运行在客户端(浏览器)的编程语言(脚本语言),实现人机交互效果。
  2. 组成:
  • ECMAScript: 规定了js基础语法核心知识(如:变量,分支语句ect)
  • Web APIS:DOM(操作文档,比如 对页面元素进行移动,大小,添加删除等操作)、BOM(操作浏览器,比如页面弹窗,检查窗口宽度、存储数据到浏览器等等)
  1. 脚本语言的定义:无需编译,可以有某种解释器直接执行(sql、python、html、css、JavaScript)直接有某种解释器(引擎)解释执行,逐行从上往下解释执行。

作用:

  1. 网页特效(监听用户的一些行为让网页做出对应的反馈)
  2. 表单验证(针对表单数据的合法性进行判断)
  3. 数据交互(获取后台的数据,渲染到前端)
  4. 服务端编程(node.js)

具有的特点:

  1. 解释型脚本语言:
    JavaScript是一种解释型脚本语言,与C、C++等语言需要先编译在运行不同,使用JavaScript编写的代码不需要运行,可以直接运行。
  2. 弱类型:
    JavaScript是一种弱类型的编程语言,对使用的数据类型没有严格的要求。
  3. 面向对象:
    JavaScript是一种面向对象语言,使用JavaScript不仅可以创建对象,也能操作使用已有的对象。
  4. 动态性:
    JavaScript是一种采用事件驱动的脚本语言,它不需要借助Web服务器就可以对用户的输做出响应。例如我们再访问一个网页时,通过鼠标再网页中进行点击或滚动窗口时,通过JavaScript可以直接对这些事件做出响应。
  5. 跨平台性:
    JavaScript不依赖操作系统,再浏览器中就可以运行。因此一个JavaScript脚本再编写完成后可以再任意系统上运行,只需要系统上的浏览器支持JavaScript即可。

执行顺序:

  • JavaScript代码执行顺序:
    可以写在HTML文档<head>标签与<body>标签中,写在不同的标签中的执行顺序不同

书写位置(3种):

  • 内部(写在body里面):

          <body>
              <!--内部js-->
              <script>
                  <!-- alert:页面弹出警示框-->
                  alert("你好,js")
              </script>
          </body>
    

1.注意事项:
我们将<script>尽量放在HTML文件的底部附近的原因是浏览器会按照代码在文件中的顺序加载HTML,如果先加载的JavaScript期望修改其下方的HTML,那么它可能由于HTML尚未被加载而失败。

  • 外部(通过src引入外部js文件):

      <body>
          <!-- 通过src引入外部js文件-->
          <script src="./myjs/my.js"></script>
      </body>
    
  1. 注意事项:
    <script>标签中间无需写代码,否则会被忽略!外部的JavaScript会使代码更加有序,更易于复用,且没有脚本的混合,HTML也会更加易于读。
  • 内联(代码写在标签内部):

      <body>
          <button onclick="alert('逗你玩玩')">点击我有好运喔</button>
      </body>
    

输入、输出语法:

  • 理解:人和计算机的交互,用户用过键盘、鼠标等向计算机输入信息,计算机处理后再展示结果给用户,这便是一次输入和输出的过程。
  1. 输出语法:

     <body>
         <script>
             // 1.文档输出内容(在后面Ctrl+/ 生成单行注释):向body内输出内容     1. 注意事项:如果输出的内容写的是标签,也会被解析成网页元素
    
             document.write('我是图图小淘气')
             document.write('<h1>面对世界很好奇</h1>')
    
             // 2.alert直接输出 :页面弹出警告对话框
    
             alter('图图真棒')
    
             /*3.控制台打印输出 给程序员调试使用 (开始shift+alt+a生成多行注解)*/
    
             console.log('看一看对与不对')
             console.log('客户端又看不到的地方')   
    
         </script>
     </body>
    

总之,JavaScript 可以通过不同的方式来输出数据:

  • 使用 window.alert() 弹出警告框。

  • 使用 document.write() 方法将内容写到 HTML 文档中。

  • 使用 innerHTML 写入到 HTML 元素。

如需从 JavaScript 访问某个 HTML 元素,您可以使用 document.getElementById(id) 方法。请使用 “id” 属性来标识 HTML 元素,并 innerHTML 来获取或插入元素内容:

        <!DOCTYPE html>
        <html>
        <body>

        <h1>我的第一个 Web 页面</h1>

        <p id="demo">我的第一个段落</p>

        <script>
        document.getElementById("demo").innerHTML = "段落已修改。";
        </script>

        </body>
        </html>
  • 使用 console.log() 写入到浏览器的控制台。
  1. 输入语句:
    显示一个对话框,对话框中包含一条文字信息,用来提示用户输入信息

                     prompt('请输入你的姓名:')
    

变量:

  • 含义:变量是计算机存储数据容器,注意:变量不是数据本身,它仅仅是一个用于存储数据的容器,可以理解为一个个用来装东西的子箱子。

  • 声明(var 、let、const):

              let age=18    //注意let 不允许多次声明一个变量,但是var可以多次声明同一个变量(不太好)
    
  • 精辟: var既可以重新声明,也可以重新赋值;let不能重新声明,可以重新赋值;const 既不能重新声明,也不能重新赋值(专一的好家伙,定义常量时必须赋值)。

  • 变量赋值之后如何更新新值:
    直接给它一个不同的值更新它的值,例如:

              lat age = 18
               age = 19 
    
  • 本质:是程序再内存中申请的一块用来存放数据的小空间

  • 变量命名规则与规范:

  1. 规则:
    • 不能用关键字(有特殊含义的字符,如js中的let、var、const、if、for等)
    • 只能用下划线、字母、数字、$组成,且数字不能开头
    • 字母严格区分大小写
  2. 规范:
    • 起名要由意义
    • 遵守小驼峰命名法(第一个单词的首字母小写,后面每一个单词的首字母大写。如:userName)

优先级(同时使用多个运算符编写程序时,会按照某种顺序先后执行):

JavaScript中优先级越高越先被执行,优先级相同时从左向右执行。

  • (乘、除、取余优先级相同)>(加、减优先级相同)
  • 使用()可以提升优先级
  • 总之:先乘除后加减,有括号的先算括号里面的

数据类型

  • 数字类型(number):其中包括整型数和浮点数
    1.注意事项:
    NaN代表一个计算错误。它是一个不正确的或者是一个未被定义的数字操作所得到的结果(NaN是粘性的:任何对NaN的操作都会返回Nan)。

  • 字符串类型(String):
    通过单引号(’’)、双引号(””)、或则反引号(``)包裹的数据都叫字符串,单双引号没有本质上的区别。
    2.注意事项:

  • 无论是单双引号都必须成对使用

  • 单引号/双引号可以互相嵌套,但是不可以自己嵌套自己。

  • 必要时可以使用转义字符\,输出单引号或双引号

          console.log('我是图图"小淘气"')
          console.log("我是图图'小淘气'")
          console.log('我是图图\'小淘气\'')
    

字符串的拼接:

        let age = 18
        //方法一:模板字符串(外面用反引号``,里面用${变量名})
        document.write(`我今年${age}岁了`)  
        //方法二;直接用+号拼接法
        document.write('我今年' + age + '岁了')
  • 布尔类型(Boolean):
    有两个固定的值,表示为真时(true),表示为假时(false)

  • 未定义类型(undefined):
    未定义是比较特殊的类型,只有一个值undefined。(用在只声明变量,不赋值的情况下,变量的默认值为undefined

  • 空类型(null):
    JavaScript中的null仅仅是一代表”无”,”空”,”值未知”的特殊值
    3.注意事项:

              null和undefined区别:
              undefined表示`没有赋值`(还有就是如果检测到变量是undefined就说明没有值传过来)
              null表示`赋值了,但是内容为空`(官方解释:把null作为尚未创建的对象,说白了就是,有一个变量里面存放的是一个对象呢,但是对象还没有创建好,可以先给个null,后面再来赋值)
    
              console.log(undefined + 1)  //结果为NaN
              console.log(null + 1)  //结果为1,null是赋了值,但是值为空
    

强制类型转换

  • 强制类型转换是指将一个数据类型强制转换为其他的数据类型。一般是指,将其它的数据类型转换成为String、Number、Boolean。

  • 转换为String类型:
    有三种方式:toString()、String()、拼串。

  1. toString():
    调用被转换数据类型的toString()方法,该方法不会影响到原变量,它会将转换的结果返回,但是要注意的是:null和undefined这两个值没有toString()方法,如果调用他们的方法会报错。

    let a =1234
    a=a.toString()
    console.log(a)
    console.log(typeof a)

  2. String():
    调用该函数,并将被转换的数据作为参数传递给函数,使用String()函数作强制转换时,对于Number和Boolean实际上就是调用toString()方法,但是对于null和undefined,就不会调用toString()方法,而是会将null和undefined直接转换为:”null”、”undefined”。

    let a =1234
    a=String(a)
    console.log(a)
    console.log(typeof a)

  3. 为任何数据类型+""

                     let a=1234
                     a=a+""
                     console.log(a)
                     console.log(typeof a)
    
  • 转换为Number类型:
    有三个函数可以把非数值转换为数值:Number()、parseInt()、parseFloat()。Number()可以用用来转换任意类型的数据,而后两只能用于转换字符串。
  1. Number()函数:

    • 字符串 –> 数字

                1.如果是纯数字的字符串,则直接转换为数字
                2.如果字符串中有非数字的内容,则转换为NaN
                3.如果是字符串是一个空串或者是一个全是空格的字符串,则转换为0
      
    • Boolean –>数字

                1.true-->1
                2.false-->0
      
    • null –>数字

                null -->0
      
    • undefined –>数字

                undefined  -->NaN
      
  2. parseInt():
    把一个字符串转换为一个整数

  3. parseFloat():
    把一个字符串转换为一个浮点数

注意:

            如果是对非String类型使用parseInt()或者parseFloat(),它会先将其转换为String然后再操作。
  • 转换为Boolean类型:
    只使用Boolean()函数
  1. Boolean()函数:

    • 数字–>Boolean

除了0和NaN,其余都是true
- 字符串–>Boolean
除了空串,其余都是true
- null 和 undefined都会转换为false
- 对象也会转换为true

控制台输出语句和检测数据类型

  • 用过typeof关键字检测数据类型
    typeof运算符可以返回被检测的数据类型。它支持两种语法形式
  1. 作为运算符: typeof x (常用的写法)
  2. 函数形式: typeof(x)
    总之:最后得到的结果都是一样的

类型转换

  • 隐式转换:某些运算符被执行时,系统内部自动将数据类型进行转换。
  • 规则:
    1. +号两边只要有一个是字符串,都会把另外一个转成字符串
    2. 除了+号外的算术运算符,比如 - * /等都会把数据转成数字类型
  • 小技巧:
    由于转换类型不明确,会有点小坑:
    1. +号作为正号解析可以转成数字型

    2. 任何数据和字符串 相加结果都是字符串

         console.log('one'+1)  //one1
         console.log(2-'2')    //0
         console.log(+12)     //12
         console(+'123')      //123  转换成数字型
      

赋值运算符:(对变量进行赋值的运算符)

  • 一元运算符:(自增,自减)

     <!-- 前置自增和后置自增的在运算时区别,but再单独使用的时候没有差别 -->
     let i =1
     console.log(++i + 2) //结果是4
     //注意i=2, i先自加1,变为2之后,在和后面的2相加
    
     console.log(i++ + 2)  //结果是3
     注意此时i=1 ,先和2相加,先运算输出完毕后,i再自加是2
    
  • 比较运算符:(比较结果为Boolean型,即只会得到true 或 false)
    其他的都一样,只有以下特殊的:

      ==:左右两边值是否相等
      ===:左右两边值是否类型和值都相等
      !==:左右两边是否不全等
      //对比:
      =:是赋值
      ==:是判断
      ===:是全等(开发中判断是否相等,强烈推荐使用===)
    

注意:

            <!-- 不同类型之间比较会发生隐式转换,最终把数据隐式转换成number类型再比较 例如: 2=='2'结果为true  -->
            <!-- 字符串比较的字符对应的ASCII码:从左往右一次比较,如果第一位一样在比较第二位,以此类推 -->
            <!-- NaN不等于任何值,包括它本身(只要涉及到全为false) -->
            <!-- 尽量不要比较小数,因为小数有精度问题 -->
  • 逻辑运算符:

          &&(一假为假)     ||(一真为真)     !(取反)
    

运算符优先级

  1. 小括号
  2. 一元运算符(++ – !(优先级高))
  3. 算数运算符(先 * / % 后 + -)
  4. 关系运算符(> >= < <=)
  5. 相等运算符(== != === !==)
  6. 逻辑运算符(先&& 后 ||)
  7. 赋值运算符(=)
  8. 逗号运算符(,)

条件语句

  • 含义:通过判断指定表达式的值来决定执行还是跳过某些语句,最基本的语句有:
  1. if…else
  2. switch …case(需要注意的是一旦符合case的条件程序会一直运行到结束,所以我们一般会在case中添加break作为语句的结束,判断值一定是要
    ===的)

循环语句

  • 含义:只要满足一定的条件将会一直执行,最基本的语句有:
  1. while
  2. do …while
  3. for

跳转控制

  • break:结束最近的一次循环,可以在循环和switch语句中使用
  • continue:结束本次循环,执行下一次循环,只能在循环中使用

函数

  • 作用:声明一个需要的函数,可以把具有相同或相似的逻辑代码“包裹起来”,通过函数调用执行,做到精简代码方便复用。function()->是被设计为执行特定任务的代码块

  • 声明:

              function 函数名(){
                  方法体
              }
              <!-- 调用 -->
              函数名()
              <!-- 函数的复用代码和循环重复代码用什么不同:
              - 循环代码写完立即执行,不能很方便控制执行位置
              - 函数随时调用,随时执行,可重复调用
               -->
    
  • 有返回值的函数

  1. 在函数体中使用return关键字能将内部的执行结果交给函数外部使用

         function getTotalPrice(x,y){
             return x+y
         }
         let sum=getTotalPrice(1,2)
         console.log(sum)  //结果为3
    
  2. return 后面代码不会在被执行,会立即结束当前函数,所以return后面的数据不要换行写

  3. return 函数可以没有return,这种情况函数默认返回值为undefined,例如:

         function fn(){
    
         }
         let re = fn()
         console.log(re)   //返回结果为undefined
    

使用误区

  1. JavaScript中允许在字符串中使用断行语句:

                 var x =
                 "Hello World!";
    

但是,在字符串中直接使用回车换行是会报错的;所以字符串断行需要使用反斜杠(),如下所示:

                var x = "Hello \
                World!";
  1. Undefined 不是 Null
    在 JavaScript 中, null 用于对象, undefined 用于变量,属性和方法。对象只有被定义才有可能为 null,否则为 undefined。

             if (typeof myObj !== "undefined" && myObj !== null) 
    
  2. 程序块作用域
    在每个代码块中 JavaScript 不会创建一个新的作用域,一般各个代码块的作用域都是全局的。以下代码的的变量 i 返回 10,而不是 undefined:

             实例
             for (var i = 0; i < 10; i++) {
                 // some code
             }
             return i;
    

this关键字:

在JavaScript中this不是固定不变的,它会随着执行环境的改变而改变

  • 在方法中,this表示该方法所属的对象

              var person = {
              firstName: "John",
              lastName : "Doe",
              id       : 5566,
              fullName : function() {
                  return this.firstName + " " + this.lastName;
              }
              };
    
  • 如果单独使用,this表示全局对象

              //在浏览器中,window就是该全局对象为`object Window`
              var x = this
              //严格模式下,如果单独使用,this 也是指向全局(Global)对象。
              "use strict";
              var x = this;
    
  • 在函数中,this表示全局对象

              //在函数中,函数的所属者默认绑定到this上
              function myFunction(){
                  return this;
              }
    
  • 在函数中,严格模式下,this是undefined

              //严格模式下函数是没有绑定到 this 上,这时候 this 是 undefined。
              "use strict";
              function myFunction() {
              return this;
              }
    
  • 在事件中,this表示接受事件的元素

              在 HTML 事件句柄中,this 指向了接收事件的 HTML 元素:
              <button onclick = "this.style.display='none'">点我后我就消失不见了</button>
    
  • 类似call() 和 apply() 方法可以将this引用到任何对象,即显示函数绑定
    在JavaScript中函数也是对象,对象则有方法,call()、apply()就是函数对象方法。这两个方法异常强大,允许切换函数执行的上下文环境,即切换this绑定的对象。例如:

             <p id="demo"></p>
              <script>
              var person1 = {
              fullName: function() {
                  return this.firstName + " " + this.lastName;
              }
              }
              var person2 = {
              firstName:"John",
              lastName: "Doe",
              }
              var x = person1.fullName.call(person2); //// 返回 "John Doe"
              document.getElementById("demo").innerHTML = x; 
              </script>
    

表单及其验证、验证API

  • 表单验证:HTML表单验证可以通过JavaScript来完成
    HTML 表单验证可以通过 JavaScript 来完成。
    以下实例代码用于判断表单字段(fname)值是否存在, 如果不存在,就弹出信息,阻止表单提交:

          <!DOCTYPE html>
              <html>
              <head>
              <meta charset="utf-8">
              <script>
              function validateForm() {
                  var x = document.forms["myForm"]["fname"].value;
                  if (x == null || x == "") {
                      alert("需要输入名字。");
                      return false;
                  }
              }
              </script>
              </head>
              <body>
    
              <form name="myForm" action="demo_form.php"
              onsubmit="return validateForm()" method="post">
              名字: <input type="text" name="fname">
              <input type="submit" value="提交">
              </form>
    
              </body>
              </html>
    
  • HTML表单自动验证:
    HTML表单验证也可以通过浏览器来自动完成,就是设置必填required,判断其值是否为空,为空时候required属性会阻止表单的提交:

              <form action="demo_from.php" method="post">
              <input type="text" name="fname" required="required">
              <input type="submit" value="提交">
              </form>
    
              1.E-mail 验证:在数据在送往服务器前对数据通过条件自动检测
              下面的函数检查输入的数据是否符合电子邮件地址的基本语法。
    
              意思就是说,输入的数据必须包含 @ 符号和点号(.)。同时,@ 不可以是邮件地址的首字符,并且 @ 之后需有至少一个点号:
    
              function validateForm(){
              var x=document.forms["myForm"]["email"].value;
              var atpos=x.indexOf("@");
              var dotpos=x.lastIndexOf(".");
              if (atpos<1 || dotpos<atpos+2 || dotpos+2>=x.length){
                  alert("不是一个有效的 e-mail 地址");
                  return false;
              }
              }
              下面是连同 HTML 表单的完整代码:
    
              实例
              <form name="myForm" action="demo-form.php" onsubmit="return validateForm();" method="post">
                  Email: <input type="text" name="email">
                  <input type="submit" value="提交">
              </form>
    
  • 数据验证
    数据验证用于确保用户输入的数据是有效的。典型的数据验证有:

  1. 必需字段是否有输入?
  2. 用户是否输入了合法的数据?
  3. 在数字字段是否输入了文本?
    大多数情况下,数据验证用于确保用户正确输入数据。
    数据验证可以使用不同方法来定义,并通过多种方式来调用。例如:

服务端数据验证是在数据提交到服务器上后再验证。
客户端数据验证是在数据发送到服务器前,在浏览器上完成验证。

  • HTML 约束验证
    HTML5 新增了 HTML 表单的验证方式:约束验证(constraint validation)。
    约束验证是表单被提交时浏览器用来实现验证的一种算法。
    HTML 约束验证基于:
  1. HTML 输入属性
  2. CSS 伪类选择器
  3. DOM 属性和方法

验证API:

  • 约束验证 DOM 方法
  1. checkValidity():如果 input 元素中的数据是合法的返回 true,否则返回 false。
  2. setCustomValidity()
    设置 input 元素的 validationMessage 属性,用于自定义错误提示信息的方法。

使用 setCustomValidity 设置了自定义提示后,validity.customError 就会变成 true,checkValidity 总是会返回 false。如果要重新判断需要取消自定义提示,方式如下:

setCustomValidity(‘’)
setCustomValidity(null)
setCustomValidity(undefined)

            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>Document</title>
                <script>
                    function myFunction(){
                        var x =document.getElementById("id1");
                        x.setCustomValidity("");  //使用前先取消定义,否则下次点击checkValidity总会返回false,若是不取消下次直接默认checkValidity==false 
                        if(x.checkValidity() == false){
                            x.setCustomValidity("错误");
                            document.getElementById("demo").innerHTML=x.validationMessage;
                        }else{
                            x.setCustomValidity("对啦");
                            document.getElementById("demo").innerHTML=x.validationMessage;
                        }
                    }
                </script>
            </head>

            <body>
                <p>输入数字并点击验证按钮:</p>
                <input type="number" id="id1" max="300" min="100" required>
                <button type="submit" onclick="myFunction()">点我</button>
                <p>如果输入数字100-300以外,会提示错误信息。</p>
                <p id="demo"></p>
                
            </body>
            </html>

void()

  • 该操作指定要计算一个表达式但是不返回值(但是括号内的表达式还是要运行的)。

  • javascript:void(0):相当于一个死链接,什么都不会发生。

              <a href="javascript:void(0)">单击此处什么也不会发生</a>
    
  • 区别href=”#”与href=”javascript:void(0)”

#包含了一个位置信息,默认的锚是#top 也就是网页的上端。

而javascript:void(0), 仅仅表示一个死链接。

在页面很长的时候会使用 # 来定位页面的具体位置,格式为:# + id。

如果你要定义一个死链接请使用 javascript:void(0) 。

            <a href="javascript:void(0);">点我没有反应的!</a>
            <a href="#pos">点我定位到指定位置!</a>
            <br>
            ...
            <br>
            <p id="pos">尾部定位点</p>

正则表达式:

  • 含义:正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法,以及 String 的 match、matchAll、replace、replaceAll、search 和 split 方法。
  • 定义:
  1. 字面量定义:
    let reg = /while/i(i表示不区分大小写)
  2. 构造函数定义:
    let reg = new RegExp(‘while’,’i’)
  • 正则表达式的方法:
  1. exec():

                 let reg = /while/i
                 let str = 'while and while'
                 console.log(reg.exec(str))  //结果为['while',index:0,input:'while and while',groups:undefined]
    
             <!-- 注意:
             1. 正则表达式的exec方法会返回一个数组,数组的第一项是匹配到的字符串,第二项是匹配到的字符串的起始位置,第三项是匹配到的字符串的结束位置,第四项是匹配到的字符串的分组。
             2. 正则表达式的exec方法会从字符串的开头开始匹配,如果匹配到了就返回匹配到的字符串,否则返回null。
    
  2. test():

             let reg = /while/i
             let str = 'while and while'
             console.log(reg.test(str))  //结果为true 
             <!-- 注意:
             1. 正则表达式的test方法会返回一个布尔值,true表示匹配到了,false表示没有匹配到。
    
  3. match():

             let reg = /while/i
             let str = 'while and while'
             console.log(str.match(reg))  //结果为['while',index:0,input:'while and while',groups:undefined]
    
             <!-- 注意:
             1. 正则表达式的match方法会返回一个数组,数组的第一项是匹配到的字符串,第二项是匹配到的字符串的起始位置,第三项是匹配到的字符串的结束位置,第四项是匹配到的字符串的分组。
             2. 正则表达式的match方法会从字符串的开头开始匹配,如果匹配到了就返回匹配到的字符串,否则返回null。
             3. 正则表达式的match方法会返回一个数组,数组的第一项是匹配到的字符串,第二项是匹配到的字符串的起始位置,第三项是匹配到的字符串的结束位置,第四项是匹配到的字符串的分组。
    
  4. replace():

             let reg = /while/i
             let str = 'while and while'
             console.log(str.replace(reg,'hi'))  //结果为hi and while
             <!-- 注意:
             1. 正则表达式的replace方法会返回一个字符串,字符串的内容是替换后的字符串。
             2. 正则表达式的replace方法会从字符串的开头开始匹配,如果匹配到了就替换,否则返回原字符串。
             3. 正则表达式的replace方法会返回一个字符串,字符串的内容是替换后的字符串。
    
  5. split():

             let reg = /while/i
             let str = 'while and while'
             console.log(str.split(reg))  //结果为['', ' and ']
             <!-- 注意:
             1. 正则表达式的split方法会返回一个数组,数组的内容是分割后的字符串。
             2. 正则表达式的split方法会从字符串的开头开始匹配,如果匹配到了就分割,否则返回原字符串。
             3. 正则表达式的split方法会返回一个数组,数组的内容是分割后的字符串。
    
  6. search():

             let reg = /while/i
             let str = 'while and while'
             console.log(str.search(reg))  //结果为0
             <!-- 注意:
             1. 正则表达式的search方法会返回一个数字,数字的内容是匹配到的字符串的起始位置。
             2. 正则表达式的search方法会从字符串的开头开始匹配,如果匹配到了就返回匹配到的字符串的起始位置,否则返回-1。
    

7.正则表达式常用量词的含义:

量词 含义
* 出现0次或连续多次,等价于:{0,}。
+ 出现至少一次,等价于:{1,}。
? 出现0次或则1次。例如,”do(es)?” 可以匹配 “do” 或 “does” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。
8.正则表达式常用元字符的含义:
元字符 含义
. 匹配除换行符 \n 之外的任何单字符。要匹配包括 ‘\n’ 在内的任何字符,请使用像”(.
\w 匹配字母或数字或下划线或汉字 等价于’[A-Za-z0-9_]’。
\W 匹配任意不是字母,数字,下划线,汉字的字符
\s 匹配任意的空白符
\S 匹配任意不是空白符的字符
\d 匹配数字
\D 匹配任意非数字的字符
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
\n 换行符
\t 制表符
\r 回车符
\f 换页符
\v 垂直制表符
\e 转义符
\0 空字符
注解和反射

注解和反射

注解的含义:

  • Annotation的作用:不是程序本身,可以对程序做出解释;可以被其他程序(比如:编译器等)读取,注解可以用来修饰类、方法、变量、参数等,可以用来检查代码质量、约束开发者的编程风格、提高代码的可读性和可维护性(检查、约束)。
  • 格式:以“@注释名”在代码中存在,还可以在后面添加一些参数值
  • 使用范围:可以附加在package、class、method、filed等上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问

内置注解:

  • @Override: 定义在java.lang.Override中,此注解只适用于修饰方法,表示一个方法声明打算重写超类中的另外一个方法声明。

  • @Deprecated:定义在Java.lang.Deprecated中,此注解可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素(通常事因为它很危险或者存在更好的选择),但是可以此方法可以运行。

  • @SuppressWarnings:定义在Java.lang.SuppressWarnings中,用来抑制编译时的警告信息(后面要有参数才能正确使用),例如:

      @SuppressWarnings("all");
      @SuppressWarnings("unchecked");
      @SuppressWarnings("unchecked","deprecation");
    

元注解:

  • 作用:负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明。

  • 类型:@Target ,@Retention, @Documented ,@Inherited (这些类型和他们所支持的类在Java。狼。annotation包中可以找到。)

          @Target:用于描述注解的使用范围(即被描述的代码可以用在那些地方)
    
          @Retention:表示需要在说明级别保存该注解信息,用于描述注解的生命周期(Runtime>class>source)
          @Document:说明该注解将被包含在Javadoc 中
          @Inherited:说明子类可以继承父类中的该注解
    
  • 格式:
    @Target(value ={ElementType.METHOD,ElementType.TYPE})
    @Retention(value=RetentionPolicy.RUNTIME)

自定义注解:

  • 用法:使用@interface自定义注解时,自动继承java.lang.annotation.Annotation接口
  • 归纳:
  1. @interface用来声明一个注解,格式:public @interface 注解名{定义内容}
  2. 其中的每一个方法实际上是声明一个配置参数
  3. 方法的名称就是参数的名称
  4. 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
  5. 可以通过default来声明参数的默认值
  6. 如果只有一个参数成员,一般参数名为value
  7. 注解元素必须要由值,定义注解元素时,经常使用空字符串0作为默认值
  • 代码如下:
    package com.st.test;

    //测试自定义注解

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    public class Test03 {
    //注解可以显示赋值,如果没有默认值(default 数值),我们就必须给注解赋值
    @MyAnnotation2(name=”李明” ,schools ={“湖南大学”,”清华大学”} )
    public void test(){}

    //如果自定义的注解只有一个值的时候可以使用value表示,在方法注解表示的时候可以直接省略value
    @MyAnnotation3(“李明”)
    public void test2(){}
    }

    @Target(value={ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotation2{
    //注解的参数:参数类型+参数名();
    String name() ; //赋值为李明
    int age() default 0;
    int id() default -1; //如果默认值为-1,则代表不存在

    String[] schools();
    }
    @Target(value={ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotation3{
    String value();
    }

java反射

  • 特点:Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得此类似动态语言的特性。Java的动态性让在编程的时候更加灵活!

  • 反射:被视为是动态语言的关键,反射机制允许程序在执行期间借助于’Reflection API’获取任何类的内部信息,并直接操作任意对象的内部属性及方法

           Class c=Class.forName("java.lang.String")
    

    加载完类之后,在堆内存的方法中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,通过镜子看到类的结构,所以形象的称为:反射

  • 对比:

  1. 正常的方式:先引用需要的“包类”名称——> 通过new实例化——> 取得实例化对象
  2. 反射方式: 实例化对象——> getClass()方法——> 得到完整的“包类”名称。
  • 反射机制在运行时提供的功能:
  1. 判断任意一个对象所属的类
  2. 可构造任意一个类的对象
  3. 可判断任意一个类所具有的成员变量和方法
  4. 可获取泛型信息
  5. 可调用任意一个对象的成员变量和方法
  6. 可处理注解
  7. 生成动态代理(AOP)
  • 反射使用流程:
  1. 获取class 对象:首先获取目标对象类的class对象

  2. 获取成员信息:通过 class对象,可以获取类的字段、方法、构造器等信息

  3. 操作成员:通过反射API可以读取和修改字段的值、调用方法以及创建对象

  • 反射API:
  1. java.lang.Class:表示类的对象。提供了方法来获取类的字段、方法、构造函数等
  2. java.lang.reflect.Method:表示类的方法。提供了调用方法的能力。
  3. java.lang.reflect.Field:表示类的字段(属性)。提供了访问和修改字段的能力。
  4. java.lang.reflect.Constructor:表示类的构造函数。提供了创建对象的能力。
  • 创建对象的五种方式: new、newInstance、clone、反序列化、反射。

  • 代码:

        package com.st.reflection;
        public class Test002 {
        public static void main(String[] args) throws ClassNotFoundException {
      //通过反射获取类的Class对象
     Class c1=Class.forName("com.st.reflection.User");
      System.out.println(c1);
    
     //查看他们是不是属于同一个类,就可以查看他们的哈希一不一样
      Class c2=Class.forName("com.st.reflection.User");
      Class c3=Class.forName("com.st.reflection.User");
      Class c4=Class.forName("com.st.reflection.User");
     //结果是一样的
      //一个类在内存中只有一个Class对象,一个类被加载之后,类的整个结构都会被封装在Class对象中
      System.out.println(c2.hashCode());
      System.out.println(c3.hashCode());
      System.out.println(c4.hashCode());
       }
    }
      
      //实体类:pojo entity
      class User{
       private String name;
       private int id;
       private  int age;
    
      public User(){
    
       }
      public User(String name, int id, int age) {
      this.name = name;
      this.id = id;
      this.age = age;
      }
    
      public String getName() {
          return name;
      }
    
      public int getId() {
          return id;
      }
    
      public int getAge() {
          return age;
      }
    
      public void setName(String name) {
          this.name = name;
      }
    
      public void setId(int id) {
          this.id = id;
      }
    
      public void setAge(int age) {
          this.age = age;
      }
    
      @Override
      public String toString() {
          return "User{" +
                  "name='" + name + '\'' +
                  ", id=" + id +
                  ", age=" + age +
                  '}';
      }
    }
    

获取Class类的实例

  • 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高

              Class clazz =Person.class;
    
  • 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

              Class clazz=Class.forName("demo01.Student");
    
  • 已知某个类的实例,调用实例的getClass()方法获取Class对象

              Classclazz=person.getClass();
    
  • 内置基本数据类型可以用类名.TYPE

  • 还可以利用ClassLoader

  • 综合示例:

              package com.st.reflection;
              //测试Class 类的创建方式有哪些
              public class Test003 {
                  public static void main(String[] args) throws ClassNotFoundException {
                      Person person=new Student();
                      System.out.println("这个人是:"+person.name);
    
                      //方式一:通过对象获得
                      Class c1=person.getClass();
                      System.out.println(c1.hashCode());
    
                      //方式二:通过forname获得
                      Class c2 = Class.forName("com.st.reflection.Student");
                      System.out.println(c2.hashCode());
    
                      //方式三:通过类名。class 获得
                      Class<Student> c3 = Student.class;
                      System.out.println(c3.hashCode());
    
                      //方式四:基本内置类型的包装类都有一个Type属性
                      Class c4=Integer.TYPE;
                      System.out.println(c4.hashCode());
    
                      //获取父类类型
                      Class c5=c1.getSuperclass();
                      System.out.println(c5);
    
    
                  }
              }
    
              class Person{
                  public String name;
                  public Person(){
    
                  }
    
                  public Person(String name) {
                      this.name = name;
                  }
    
                  @Override
                  public String toString() {
                      return "Person{" +
                              "name='" + name + '\'' +
                              '}';
                  }
              }
    
              class Student extends Person{
                  public Student(){
                      this.name="学生";
                  }
              }
              class Teacher extends Person{
                  public Teacher() {
                      this.name="老师";
                  }
              }
    

哪些类型可以有Class对象

  1. class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
  2. interface:接口
  3. []:数组(多维)
  4. enum:枚举
  5. annotation:注解@interface
  6. primitive type:基本数据类型
  7. void
Linux.md

Linux.md

前提

  • 下好虚拟机后,记得每一次做任务前最好做好快照(血泪的教训)
  • 掌握主机名/IP地址等网络信息配置的方法(一定要保证号网络可用,不然后面吃大亏,下点软件都用不来哦。)

设置IP地址:

  1. 直接在终端运行nmtui命令配置网络(图形界面自己会了)。修改后使用systemctl restart NetworkManager 命令重启,使IP更改信息生效。
  2. nmcli命令配置网络,Ubuntu 通过NetworkManager.service(简称NM)进行网络配置,包括动态和静态IP,因此Ubuntu系统必循开启NM,否则无法使用网络,网络配置存储在/etc/NetworkManager/system-connections/目录中。
  • 用法1:查看IP
    nmcli
  • 用法2:查看connection列表
    nmcli connection show
  • 用法3:修改ens33网卡,配置方式为manual手动,设置静态IP地址,网卡,DNS信息。
  • 用法4;立即生效connection
    nmcli connection up ens33
    nmcli device reapply ens33
    nmcli device connect ens33

快捷键:

  1. Ctrl+Alt+T 组合键打开命令行终端
  2. Ctrl+l 清屏
  3. 打开控制面板的网络适配\连接: Win+R 输入ncpa.cpl
  4. 重启vm虚拟机:reboot

实施SSH远程连接:

  1. 前提Linux中有OpenSSH服务(没有自己下)
  2. 在进行远程登陆前,通过systemctl stop ufwsystemctl disable ufw `关闭防火墙功能。

文件与目录管理:

  • Linux 常用目录功能:
  1. ls(英文全拼:list files): 列出目录及文件名
  2. cd(英文全拼:change directory):切换目录
  3. pwd(英文全拼:print work directory):显示目前的目录
  4. mkdir(英文全拼:make directory):创建一个新的目录
  5. rmdir(英文全拼:remove directory):删除一个空的目录
  6. cp(英文全拼:copy file): 复制文件或目录
  7. rm(英文全拼:remove): 删除文件或目录
  8. mv(英文全拼:move file): 移动文件与目录,或修改文件与目录的名称

系统目录结构:

  1. /bin:
    bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。

  2. /boot:
    这里存放的是启动 Linux 时使用的一些核心文件,包括一些连接文件以及镜像文件。

  3. /dev :
    dev 是 Device(设备) 的缩写, 该目录下存放的是 Linux 的外部设备,在 Linux 中访问设备的方式和访问文件的方式是相同的。

  4. /etc:
    etc 是 Etcetera(等等) 的缩写,这个目录用来存放所有的系统管理所需要的配置文件和子目录。

  5. /home:
    用户的主目录,在 Linux 中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的,如上图中的 alice、bob 和 eve。

  6. /lib:
    lib 是 Library(库) 的缩写这个目录里存放着系统最基本的动态连接共享库,其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库。

  7. /media:
    linux 系统会自动识别一些设备,例如U盘、光驱等等,当识别后,Linux 会把识别的设备挂载到这个目录下。

  8. /lost+found:
    这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件。

  9. /mnt:
    系统提供该目录是为了让用户临时挂载别的文件系统的,我们可以将光驱挂载在 /mnt/ 上,然后进入该目录就可以查看光驱里的内容了。

  10. /opt:
    opt 是 optional(可选) 的缩写,这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。

  11. /proc:
    proc 是 Processes(进程) 的缩写,/proc 是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。
    这个目录的内容不在硬盘上而是在内存里,我们也可以直接修改里面的某些文件,比如可以通过下面的命令来屏蔽主机的ping命令,使别人无法ping你的机器:

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
12. /root:
该目录为系统管理员,也称作超级权限者的用户主目录。

  1. /sbin:
    s 就是 Super User 的意思,是 Superuser Binaries (超级用户的二进制文件) 的缩写,这里存放的是系统管理员使用的系统管理程序。

  2. /selinux:
    这个目录是 Redhat/CentOS 所特有的目录,Selinux 是一个安全机制,类似于 windows 的防火墙,但是这套机制比较复杂,这个目录就是存放selinux相关的文件的。

  3. /srv:
    该目录存放一些服务启动之后需要提取的数据。

16./sys:
这是 Linux2.6 内核的一个很大的变化。该目录下安装了 2.6 内核中新出现的一个文件系统 sysfs 。
sysfs 文件系统集成了下面3种文件系统的信息:针对进程信息的 proc 文件系统、针对设备的 devfs 文件系统以及针对伪终端的 devpts 文件系统。
该文件系统是内核设备树的一个直观反映。
当一个内核对象被创建的时候,对应的文件和目录也在内核对象子系统中被创建。

  1. /tmp:
    tmp 是 temporary(临时) 的缩写这个目录是用来存放一些临时文件的。

  2. /usr:
    usr 是 unix system resources(unix 系统资源) 的缩写,这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于 windows 下的 program files 目录。

  3. /usr/bin:
    系统用户使用的应用程序。

  4. /usr/sbin:
    超级用户使用的比较高级的管理程序和系统守护程序。

  5. /usr/src:
    内核源代码默认的放置目录。

  6. /var:
    var 是 variable(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。

  7. /run:
    是一个临时文件系统,存储系统启动以来的信息。当系统重启时,这个目录下的文件应该被删掉或清除。如果你的系统上有 /var/run 目录,应该让它指向 run。

  • 总结:

在 Linux 系统中,有几个目录是比较重要的,平时需要注意不要误删除或者随意更改内部文件。

/etc: 上边也提到了,这个是系统中的配置文件,如果你更改了该目录下的某个文件可能会导致系统不能启动。

/bin, /sbin, /usr/bin, /usr/sbin: 这是系统预设的执行文件的放置目录,比如 ls 就是在 /bin/ls 目录下的。

值得提出的是 /bin、/usr/bin 是给系统用户使用的指令(除 root 外的通用用户),而/sbin, /usr/sbin 则是给 root 使用的指令。

/var: 这是一个非常重要的目录,系统上跑了很多程序,那么每个程序都会有相应的日志产生,而这些日志就被记录到这个目录下,具体在 /var/log 目录下,另外 mail 的预设放置也是在这里。

安装软件的步骤:

  • 以安装gcc为例:
  1. 检查是否安装了gcc
    rpm -qa|grep gcc

  2. 若是没有显示有安装包,则可以用dnf命令安装所需要的软件包,步骤如下:
    mount /dev/cdrom /media