Azhang nga gipatik nga mga artikulo

配置 bean

主要内容:
  • 配置形式:基于 XML 文件的方式;基于注解的方式(基于注解配置 bean ;基于注解来装配 bean 的属性)。
  • Bean 的配置方式:通过全类名(反射)、通过工厂方法(静态工厂方法&实例工厂方法)、FactoryBean。
  • IOC 容器 BeanFactory & ApplicationContext 概述。
  • 依赖注入的方式:属性注入;构造器注入。
  • 注入属性值细节。
  • 自动装配。
  • bean 之间的关系:继承;依赖。
  • bean 的作用域:singleton;prototype;WEB 环境作用域。
  • 使用外部属性文件。
  • spEL。
  • IOC 容器中 Bean 的生命周期。
  • Spring 4.x 新特性:泛型依赖注入。

基于 XML 文件的方式:

1、在 Spring 的 IOC 容器里配置 Bean
  • 在 xml 文件中通过 bean节点来配置 bean。

    <!--
        配置 bean
        class:bean 的全类名,通反射的方式在 IOC 容器中创建 Bean,所以要求 Bean 中必须有无参构造方法
        id:标识容器中的 bean,id唯一。
    -->
    <bean id="helloWorld" class="com.spring.beans.HelloWorld">
            <property name="name" value="Spring"/>
    </bean>
  • id:Bean 的名称。

    • 在 IOC 容器中必须是唯一的
    • 若 id 没有指定,Spring 自动将权限定性类名作为 Bean 的名字
    • id 可以指定多个名字,名字直渐可用逗号、分号、或空格分隔
2、Spring 容器
  • Spring IOC 容器读取 Bean 配置创建 Bean 实例之前,必须对它进行实例化。只有在容器实例化后,才可以从 IOC 容器里获取 Bean 实例并使用。
  • Spring 提供了两种类型的 IOC 容器实现。

    • BeanFactory:IOC 容器的基本实现。
    • ApplicationContext:提供了更多的高级特性,是 BeanFactory 的子接口。
    • BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;

      ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory。

    • 无论使用何种方式,配置文件时都相同的。
  • ApplicationContext

    • ApplicationContext 的主要实现类:

      ClassPathXmlApplicationContext:从类路径下加载配置文件

      — FileSystemXmlApplicationContext:从文件系统中加载配置文件

    • ConfigurableApplicationContext 扩展于 ApplicationContext,新增两个主要方法:refresh() 和 close(),让 ApplicationContext 具有启动、刷新和关闭上下文的能力
    • ApplicationContext 在初始化上下文时就实例化所有单例的 Bean。
    • WebApplicationContext 是专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作。
  • getBean() 方法

    参数有两种

    • 参数是 Bean 的 id;

      利用 id 定位到 IOC 容器中的 bean

      HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");
  • 参数是 类名.class;

    利用类型返回 IOC 容器中的 Bean,但要求 IOC 容器中只能有一个该类型的 Bean

    HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
3、依赖注入的方式
  • 属性注入

    • 属性注入即通过 setter 方法注入 Bean 的属性值或依赖的对象
    • 属性注入使用 <property> 元素,使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值
    • 属性注入是实际应用中最常用的注入方式

      <bean id="helloWorld" class="com.spring.beans.HelloWorld">
              <property name="name" value="Spring"/>
      </bean>
  • 构造方法注入

    • 通过构造方法注入 Bean 的属性值或依赖的对象,它保证了 Bean 实例在实例化后就可以使用。
    • 构造器注入在 <constructor-arg> 元素里生命属性

      <constructor-arg> 中没有 name 属性

      public class Car {
      
          private String brand;
          private String corp;
          private double price;
          private int maxSpeed;
      
          @Override
          public String toString() {
              return "Car{" +
                      "brand='" + brand + '\'' +
                      ", corp='" + corp + '\'' +
                      ", price=" + price +
                      ", maxSpeed=" + maxSpeed +
                      '}';
          }
      
          public Car(String brand, String corp, double price) {
              this.brand = brand;
              this.corp = corp;
              this.price = price;
          }
      
          public Car(String brand, String corp, int maxSpeed) {
              this.brand = brand;
              this.corp = corp;
              this.maxSpeed = maxSpeed;
          }
      }
      <!--<bean id="car" class="com.spring.beans.Car">
          <constructor-arg value="Audi" index="0"/>
          <constructor-arg value="ShangHai" index="1"/>
          <constructor-arg value="300000" index="2"/>
      </bean>-->
      
      <bean id="car" class="com.spring.beans.Car">
          <constructor-arg value="Audi" type="java.lang.String"/>
          <constructor-arg value="ShangHai" type="java.lang.String"/>
          <constructor-arg value="300000" type="double"/>
      </bean>
      
      <bean id="car2" class="com.spring.beans.Car">
          <constructor-arg value="BaoMa" type="java.lang.String"/>
          <constructor-arg value="ShangHai" type="java.lang.String"/>
          <constructor-arg value="300000" type="int"/>
      </bean>

      其中 index 表示构造函数中参数的位置,type 表示类型。以区分重载的构造函数。

  • 工厂方法注入(很少使用,不推荐)
4、注入属性值的细节
  • 字面值:可用字符串表示的值,可以通过 <value> 元素标签或 value 属性进行注入。

    <bean id="car2" class="com.spring.beans.Car">
        <constructor-arg value="BaoMa" type="java.lang.String"/>
        <constructor-arg value="ShangHai" type="java.lang.String"/>
        <constructor-arg type="int">
            <value>250</value>
        </constructor-arg>
    </bean>
  • 基本数据类型及其封装类、String 等类型都可以采取字面值注入方式。
  • 若字面值中包含特殊字符,可以使用 <![CDATA[]]> 把字面值包裹起来。

    <bean id="car2" class="com.spring.beans.Car">
        <constructor-arg value="BaoMa" type="java.lang.String"/>
        <constructor-arg type="java.lang.String">
            <value><![CDATA[<ShangHai^>]]></value>
        </constructor-arg>
        <constructor-arg type="int">
            <value>250</value>
        </constructor-arg>
    </bean>
5、引用其它 Bean
  • 组成应用程序的 Bean 经常需要相互协作以完成应用程序的功能,要使 Bean 能够相互访问,就必须在 Bean 配置文件中指定对 Bean 的引用。
  • 在 Bean 的配置文件中,可以通过 <ref> 或 ref 属性为 Bean 的属性或构造器参数指定对 Bean 的引用。

    <bean id="person" class="com.spring.beans.Person">
        <property name="name" value="Tom"/>
        <property name="age" value="24"/>
        <!--<property name="car" ref="car2"/>-->
        <property name="car">
            <ref bean="car2"/>
        </property>
    </bean>
  • 也可以在属性或构造器里包含 Bean 的声明,这样的 Bean 称为內部 Bean

    在属性内使用內部 Bean:

    <bean id="person" class="com.spring.beans.Person">
        <property name="name" value="Tom"/>
        <property name="age" value="24"/>
        <!--<property name="car" ref="car2"/>-->
        <!--<property name="car">-->
        <!--    <ref bean="car2"/>-->
        <!--</property>-->
        <property name="car">
            <bean class="com.spring.beans.Car">
                <constructor-arg value="Ford"/>
                <constructor-arg value="ChangAn"/>
                <constructor-arg value="200000" type="double"/>
            </bean>
        </property>
    </bean>

    在构造器内使用內部 Bean:

    public Person() {
    
    }
    
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }
    <bean id="person2" class="com.spring.beans.Person">
        <constructor-arg value="Jerry"/>
        <constructor-arg value="25"/>
        <constructor-arg ref="car"/>
    </bean>

    !注意:当 Bean实例仅仅给一个特定的属性使用时,可以将其声明为內部 Bean。內部 Bean 声明直接包含在 <property> 或 <constructor-arg> 元素里,不需要设置任何 id 或 name 属性。內部 Bean 不能使用在其他任何地方。

6、注入参数详解:null 值和级联属性
  • 可以使用专用的 <null/> 元素标签为 Bean 的字符串或其它对象类型的属性注入 bull 值

    <bean id="person2" class="com.spring.beans.Person">
        <constructor-arg value="Jerry"/>
        <constructor-arg value="25"/>
        <!--<constructor-arg ref="car"/>-->
        <constructor-arg><null/></constructor-arg>
    </bean>
  • 和 Struts、Hiberante 等框架一样,Spring 支持级联属性的配置

    <bean id="person2" class="com.spring.beans.Person">
        <constructor-arg value="Jerry"/>
        <constructor-arg value="25"/>
        <!--<constructor-arg ref="car"/>-->
        <!--<constructor-arg><null/></constructor-arg>-->
        <constructor-arg ref="car2"/>
        <property name="car.maxSpeed" value="250"/>
    </bean>

    !注意:属性需要先初始化后才可以为级联属性赋值,否则会有异常,和 Struts2 不同。

7、集合属性
  • 在 Spring 中可以通过一组内置的 xml 标签(例如:<list> , <set> , <map> )来配置集合属性。
  • 配置 Java.util.List 类型的属性,需要指定 <list> 标签,在标签里包含一些元素。这些标签可以通过 <value> 指定简单的常量值,通过<ref> 指定对其他 Bean 的引用。通过 <bean> 指定内置 Bean 定义。通过 <null/> 指定空元素。甚至可以内嵌其他集合。
  • 数组的定义和 List 一样,都使用 <list> 。
  • 配置 java.util.Set 需要使用 <set> 标签,定义元素的方法与 List 一样。

    <!-- 使用 list 节点为 List 类型的属性赋值 -->
    <bean id="person3" class="com.spring.beans.collections.Person">
        <property name="name" value="Mike"/>
        <property name="age" value="27"/>
        <property name="cars">
            <list>
                <ref bean="car"/>
                <ref bean="car2"/>
            </list>
        </property>
    </bean>
    <!-- 使用 list 节点为 List 类型的属性赋值(內部 Bean) -->
    <bean id="person3" class="com.spring.beans.collections.Person">
        <property name="name" value="Mike"/>
        <property name="age" value="27"/>
        <property name="cars">
            <list>
                <ref bean="car"/>
                <ref bean="car2"/>
                <bean class="com.spring.beans.Car">
                    <constructor-arg value="Ford"/>
                    <constructor-arg value="ChangAn"/>
                    <constructor-arg value="200000" type="double"/>
                </bean>
            </list>
        </property>
    </bean>
  • Java.util.Map 通过 <map> 标签定义,<map> 标签里可以使用多个 <entry> 作为子标签。每个条目包含一个键和一个值。
  • 必须在 <key> 标签里定义键
  • 因为键和值的类型没有限制,所以可以自由地为它们指定 <value> ,<ref> ,<bean> 或 <null> 元素。
  • 可以将 Map 的键和值作为 <entry> 的属性定义;简单常量使用 key 和 value 来定义;Bean 引用通过 key-ref 和 value-ref 属性定义
  • 使用 <props> 定义 java.util.Properties,该标签使用多个 <prop> 作为子标签。每个 <prop> 标签必须定义 key 属性。

    <!-- 使用 map 节点及 map 的 entry 子节点配置 Map 类型的成员变量 -->
    <bean id="newPerson" class="com.spring.beans.collections.NewPerson">
        <property name="name" value="Rose"/>
        <property name="age" value="28"/>
        <property name="cars">
            <map>
                <entry key="AA" value-ref="car"/>
                <entry key="BB" value-ref="car2"/>
            </map>
        </property>
    </bean>
    
    <!-- 使用 props 和 prop 子节点来为 Properties 属性赋值 -->
    <bean id="dataSource" class="com.spring.beans.collections.DataSource">
        <property name="properties">
            <props>
                <prop key="user">root</prop>
                <prop key="password">1234</prop>
                <prop key="jdbcUel">jdbc:mysql:///test</prop>
                <prop key="driverClass">com.mysql.jdbc.Driver</prop>
            </props>
        </property>
    </bean>
8、使用 utility schema 定义集合
  • 使用基本的集合标签定义集合时,不能将集合作为独立的 Bean 定义,导致其他 Bean 无法引用该集合,所以无法在不同 Bean 之间共享集合。

    <!-- 配置独立的集合 bean,以供多个 bean 进行引用,需要导入 util 命名空间 -->
    <util:list id="cars">
        <ref bean="car"/>
        <ref bean="car2"/>
    </util:list>
    <bean id="person4" class="com.spring.beans.collections.Person">
        <property name="name" value="Jack"/>
        <property name="age" value="29"/>
        <property name="cars" ref="cars"/>
    </bean>
  • 可以使用 util schema 里的集合标签定义独立的集合 Bean 。需要注意的是,必须在 <beans> 根元素里添加 util schema 定义。
9、使用 p 命名空间
  • 为了简化 XML 文件的配置,越来越多的 XML 文件采用属性而非子元素配置信息。
  • Spring 从 2.5 版本开始引用了一个新的 p 命名空间,可以通过 <bean> 元素属性的方式配置 Bean 的属性。
  • 使用 p 命名空间后,基于 XML 的配置方式将进一步简化。

    <!-- 通过 p 命名空间为 bean 的属性赋值,需要先导入 p 命名空间,相对于传统的配置方式更加的简洁 -->
    <bean id="person5" class="com.spring.beans.collections.Person" p:name="Queen" p:age="30" p:cars-ref="cars"/>
10、XML 配置里的 Bean 自动装配
  • Spring IOC 容器可以自动装配 Bean。需要做的仅仅是在 <bean> 的 autowire 属性里指定自动装配的模式
  • byType(根据类型自动装配):若 IOC 容器中有多个与目标 Bean 类型一致的 Bean。在这种情况下,Spring 将无法判定哪个 Bean 最合适该属性,所以不能执行自动装配。
  • byName(根据名称自动装配):必须将目标 Bean 的名称和属性名设置的完全相同。
  • constructor(通过构造器自动装配):当 Bean 中存在多个构造器时,此种自动装配方式将会很复杂,不推荐使用

    <bean id="address" class="com.spring.beans.autowire.Address" p:city="Beijing" p:street="HuiLongGuan"/>
    
    <bean id="car" class="com.spring.beans.autowire.Car" p:brand="Audi" p:price="300000"/>
    
    <!--手工装配-->
    <bean id="person" class="com.spring.beans.autowire.Person" p:name="Tom" p:address-ref="address" p:car-ref="car"/>
    
    <!--自动装配-->
    <!--byName: 根据名字自动装配,要求 xml 文件里的 bean 的 id 与当前 bean 的 setter 风格的属性名进行自动装配,若有匹配的,则进行自动装配;若没有匹配的,则不装配。-->
    <bean id="person" class="com.spring.beans.autowire.Person" autowire="byName"/>
    <!--byType: 根据 bean 的类型和当前 bean 的属性的类型进行自动装配,若 IOC 容器中有一个以上的类型匹配的 bean,则抛异常。-->
    <bean id="person" class="com.spring.beans.autowire.Person" autowire="byType"/>

XML 配置里的 Bean 自动装配的缺点

  • 在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性。然而,若只希望装配个别属性时,autowire 属性就不够灵活了。
  • autowire 属性要么根据类型自动装配,要么根据名称自动装配,不能两者兼而有之。
  • 一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力。
11、bean 之间的关系
  • 继承 Bean 的配置

    • Spring 允许继承 bean 的配置,被继承的 bean 称为父 bean。继承这个父 bean 的 bean 称为子 bean。
    • 子 Bean 从父 Bean中继承配置,包括 Bean 的属性配置
    • 子 Bean 也可以覆盖从父 Bean 继承过来的配置

      <bean id="address" class="com.spring.beans.autowire.Address" p:city="Beijing^" p:street="WuDaoKou"/>
      
      <!--bean 配置的继承:使用 bean 的 parent 属性指定继承哪个 bean 配置-->
      <!-- 三种例子 -->
      <bean id="address2" parent="address"/>
      <bean id="address2" p:street="DaZhongSi" parent="address"/>
      <bean id="address2" p:city="Beijing" p:street="DaZhongSi" parent="address"/>
    • 父 Bean 可以作为配置模板,也可以作为 Bean 实例。若只想把父 Bean 作为模板,可以设置 <bean> 的 abstract 属性为 true,这样 Spring 将不会实例化这个 Bean。

      <!-- abstract = true 时,该 bean 称为抽象 bean。这样的 bean 不能被 IOC 容器实例化,只用来被继承配置 -->
      <bean id="address" class="com.spring.beans.autowire.Address" p:city="Beijing^" p:street="WuDaoKou" abstract="true"/>
    • 并不是 <bean> 元素里的所有属性都会被继承。比如:autowire,abstract等。
    • 可以忽略父 Bean 的 class 属性,让子 Bean 指定自己的类,而共享相同的属性配置。但此时 abstract 必须设为 true。

      <!-- 若某一个 bean 的 class 属性没有指定,则该 bean 必须是一个抽象 bean -->
      <bean id="address" abstract="true" p:city="Beijing^" p:street="WuDaoKou"/>
      
      <!--bean 配置的继承:使用 bean 的 parent 属性指定继承哪个 bean 配置-->
      <bean id="address2" class="com.spring.beans.autowire.Address" parent="address"/>
  • 依赖 Bean 的配置

    • Spring 允许用户通过 depends-on 属性设定 Bean 前置依赖的 Bean,前置依赖的 Bean 会在本 Bean 实例化之前创建好。
    • 如果前置依赖于多个 Bean,则可以通过逗号,空格或的方式配置 Bean 的名称。
    • 在实际应用中实例化某个 bean 时,经常要在实例化该 bean 之前实例化另外一个 bean,因此产生了依赖关系。

      <!--要求再配置 Person 时,必须有一个关联的 Car。-->
      <bean id="person" class="com.spring.beans.autowire.Person"
            p:name="Tom" p:address-ref="address2" depends-on="car"/>
      <bean id="car" class="com.spring.beans.autowire.Car"
            p:brand="Audi" p:price="300000"/>

      注意:depends-on = ” car ” 只是指定了在获取 person 的 bean 时,要现在 IOC 容器中能获取 car 的 bean,但是并不是说就把 car 的 bean 注入到了 person 的 bean 中了,要想 person 用有 car 的 bean,需要在 person 的 bean 的配置中获取 car 的 bean。

12、bean 的作用域
  • 使用 bean 的 scope 属性来配置 bean 的作用域
  • singleton:默认值,容器初始化时创建 bean 实例,在整个容器的生命周期内只创建这一个 bean. 单例的。

    <bean id="car"
          class="com.spring.beans.autowire.Car"
          scope="singleton">
        <property name="brand" value="Audi"/>
        <property name="price" value="300000"/>
    </bean>
  • prototype:原型的,容器初始化时不创建 bean 的实例,而在每次请求时都创建一个新的 bean 实例,并返回。

    <bean id="car"
          class="com.spring.beans.autowire.Car"
          scope="prototype">
        <property name="brand" value="Audi"/>
        <property name="price" value="300000"/>
    </bean>
13、使用外部属性文件
  • 在配置文件里配置 bean 时,有时需要在 bean 的配置里混入系统部署的细节信息(例如:文件路径,数据源配置信息等)。而这些部署细节实际上需要和 bean 的配置相分离。
  • Spring 提供了一个 PropertyPlaceholderConfigurer 的 BeanFactory 后置处理器,这个处理器允许用户将 Bean 配置的部分内容外移到属性文件中。可以在 bean 配置文件里使用形式为 ${var} 的变量, PropertyPlaceholderConfigurer 从属性文件里加载属性,并使用这些属性来替换变量。
  • Spring 还允许在属性文件中使用 ${propName},以实现属性之间的相互吸引。

    xml 文件:

    <!--导入属性文件-->
    <context:property-placeholder location="db.properties"/>
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--使用外部化属性文件的属性-->
        <property name="user" value="${user}"/>
        <property name="password" value="${password}"/>
        <property name="driverClass" value="${driverClass}"/>
        <property name="jdbcUrl" value="${jdbcUrl}"/>
    </bean>

    properties 文件:

    user=root
    password=jie13727507037
    driverClass=com.mysql.cj.jdbc.Driver
    jdbcUrl=jdbc:mysql:///text?serverTimezone=UTC

    注意:mysql-connector-java 5版本之后的 driverClass 使用 com.mysql.cj.jdbc.Driver,同时还要在 jdbcUrl 后面加上 ?serverTimezone=UTC。

14、Spring 表达式语言:SpEL

Spring 表达式语言(简称 SpEL ):是一个支持运行时查询和操作对象图的强大表达式语言。
语法类似于 EL:SpEL 使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是 SpEL。
SpEL 为 bean 的属性进行动态赋值提供了便利。

通过 SpEL 可以实现:

  • 赋字面值,如:int string Boolean double等类型的值

    <bean id="address" class="com.spring.beans.spel.Address">
        <!--使用 spel 为属性赋一个字面值-->
        <property name="city" value="#{'BeiJing'}"/>
        <property name="street" value="WuDaoKou"/>
    </bean>
  • 通过 bean 的 id 对 bean 进行引用

    • 引用其他对象
  • 调用方法以及引用对象中的属性

    • 引用其他对象的属性
    • 调用其他方法,还可以链式操作
    • 调用静态方法或静态属性:通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性

      <bean id="car" class="com.spring.beans.spel.Car">
          <property name="brand" value="Audi"/>
          <property name="price" value="500000"/>
          <!--使用 spel 引用类的静态属性-->
          <property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"/>
      </bean>
  • 计算表达式的值

    • 算术运算符:+,-,*,/,%,^;
    • 加号还可以用作字符串连接
    • 比较运算符:<,>,==,<=,>=;
    • 逻辑运算符:and,or,not
    • if-else(相当于三目运算符)运算符:?:(temary), ?:(Elvis)
    • if-else(相当于三目运算符)的变体
    • 正则表达式:matches
  • 正则表达式的匹配

    <bean id="person" class="com.spring.beans.spel.Person">
    
        <!--<property name="car" ref="car"/>-->
        <!--使用 spel 来引用其他的 bean-->
        <property name="car" value="#{car}"/>
        <!--使用 spel 来引用其他 bean 的属性-->
        <property name="city" value="#{address.city}"/>
        <!--在 spel 中使用运算符-->
        <property name="info" value="#{car.price > 300000 ? '金领' : '白领'}"/>
        <property name="name" value="Tom"/>
    </bean>
15、IOC 容器中 Bean 的生命周期方法
  • Spring IOC 容器可以管理 Bean 的生命周期,Spring 允许在 Bean 生命周期的特定点执行定制的任务。
  • Spring IOC 容器对 Bean 的生命周期进行管理的过程:

    • 通过构造器或工厂方法创建 Bean 实例
    • 为 Bean 的属性设置值和其他 Bean 的引用
    • 调用 Bean 的初始化方法
    • Bean 可以使用了
    • 当容器关闭时,调用 Bean 的销毁方法
  • 在 Bean 的声明里设置 init-method 和 destroy-method 属性,为 Bean 指定初始化和销毁方法。

    xml 代码:

    <bean id="car" class="com.spring.beans.cycle.Car"
          init-method="init" destroy-method="destroy">
        <property name="brand" value="Audi"/>
    </bean>

    java 代码:

    public static void main(String[] args) {
    
        //将 ctx 转换成 ClassPathXmlApplicationContext 类型才有关闭 IOC 容器的 close() 方法
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-cycle.xml");
    
        Car car = (Car) ctx.getBean("car");
        System.out.println(car);
    
        //关闭 IOC 容器
        ctx.close();
    }

创建 bean 的后置处理器

  • Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。
  • Bean 后置处理器对 IOC 容器里的所有 Bean 实例逐一处理,而非单一实例。其典型应用是:检查 Bean 属性的正确性或根据特定的标准更改 Bean 的属性。
  • 对 Bean 后置处理器而言,需要实现 Interface BeanPostProcessor 接口。在初始化方法被调用前后,Spring 将把每个 Bean 实例分别传递给上述接口的一下两个方法:

    • public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              return null;
      }
    • public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              return null;
      }
  • Spring IOC 容器对 Bean 的生命周期进行管理的过程:

    • 通过构造器或工厂方法创建 Bean 实例
    • 为 Bean 的属性设置值和其他 Bean 的引用
    • 将 Bean 实例传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法
    • 调用 Bean 的初始化方法
    • 将 Bean 实例传递给 Bean 后置处理器的 postProcessAfterInitialization 方法
    • Bean 可以使用了
    • 当容器关闭时,调用 Bean 的销毁方法
    <!--
        实现 BeanPostProcessor 接口,并具体提供两个方法
        Object postProcessBeforeInitialization(Object bean, String beanName):init-method 之前被调用
        Object postProcessAfterInitialization(Object bean, String beanName):init-method 之后被调用
        的实现
    
        bean:bean实例本身
        beanName:IOC 容器中配置的 bean 的名字
        返回值:是实际上返回给用户的那个 bean,注意可以在以上两个方法中修改返回的 bean,甚至返回一个新的 bean
    -->
    <!-- 配置 bean 的后置处理器:不需要配置 id,IOC 容器自动识别是一个 BeanPostProcessor -->
    <bean class="com.spring.beans.cycle.MyBeanPostProcessor"/>
16、通过工厂方法配置 Bean

通过调用静态工厂方法创建 Bean

  • 调用静态工厂方法创建 Bean 是将对象创建的过程封装到静态方法中。当客户端需要对象时,只需要简单地调用静态方法,而不需要关心创建对象的细节。
  • 要声明通过静态方法创建的 Bean, 需要在 Bean 的 class 属性里指定拥有该工厂的方法的类,同时在 factory-method 属性里指定工厂方法的名称。最后,使用 <constructor-arg> 元素为该方法传递方法参数。

    /**
     * 静态工厂方法:直接调用某一个类的静态方法就可以返回 bean 的实例
     */
    public class StaticCarFactory {
    
        private static Map<String, Car> cars = new HashMap<String, Car>();
    
        static {
            cars.put("audi", new Car("audi", 300000));
            cars.put("ford", new Car("ford", 400000));
        }
    
        //静态工厂方法:
        public static Car getCar(String name) {
            return cars.get(name);
        }
    
    }
    <!--通过静态工厂方法来配置 bean, 注意不是配置静态工厂方法实例,而是配置 bean 实例-->
    <!--
        class 属性:指向静态工厂方法的全类名
        factory-method:指向静态工厂方法的名字
        constructor-arg:如果工厂方法需要传入参数,则使用 constructor-arg 来配置参数
    -->
    <bean id="car1"
          class="com.spring.beans.factory.StaticCarFactory"
          factory-method="getCar">
        <constructor-arg value="ford"/>
    </bean>

通过调用实例工厂方法创建 Bean

  • 实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节。
  • 要声明通过实例工厂方法创建的 Bean

    • 在 bean 的 factory-bean 属性里指定拥有该工厂方法的 bean
    • factory-method 属性里指定该工厂方法的名称
    • 使用 constructor-arg 元素为工厂方法传递方法参数
    /**
     * 实例工厂方法:实例工厂的方法,即先需要创建工厂本身,再调用工厂的实例方法来返回 bean 的实例
     */
    public class InstanceCarFactory {
    
        private Map<String, Car> cars = null;
    
        public InstanceCarFactory() {
            cars = new HashMap<String, Car>();
            cars.put("audi", new Car("audi", 300000));
            cars.put("ford", new Car("ford", 00000));
        }
    
        public Car getCar(String brand) {
            return cars.get(brand);
        }
    
    }
    <!--配置工厂的实例-->
    <bean id="carFactory" class="com.spring.beans.factory.InstanceCarFactory"/>
    
    <!--通过实例工厂方法来配置 bean-->
    <!--
        factory-bean:指向实例工厂方法的bean
        factory-method:指向实例工厂方法的名字
        constructor-arg:如果工厂方法需要传入参数,则使用 constructor-arg 来配置参数
    -->
    <bean id="car2" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="audi"/>
    </bean>
17、通过 FactoryBean 配置 Bean

CarFactoryBean 实现 FactoryBean 接口:

package com.spring.beans.factorybean;

import org.springframework.beans.factory.FactoryBean;

//自定义的 FactoryBean 需要实现 FactoryBean 接口
public class CarFactoryBean implements FactoryBean<Car> {

    private String brand;

    public void setBrand(String brand) {
        this.brand = brand;
    }

    //返回 bean 的对象
    @Override
    public Car getObject() throws Exception {
        return new Car(brand, 500000);
    }

    /**
     * 返回的 bean 的类型
     */
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }

    //该 bean 是否为单例的
    @Override
    public boolean isSingleton() {
        return true;
    }

}

FactoryBean 的 xml 文件:

<!--
    通过 FactoryBean 来配置 Bean 的实例
    class:指向 FactoryBean 的全类名
    property:配置 FactoryBean 的属性

    但实际返回的实例却是 FactoryBean 的 getObject() 方法返回的实例!
-->
<bean id="car" class="com.spring.beans.factorybean.CarFactoryBean">
    <property name="brand" value="BMW"/>
</bean>

IOC 和 DI

  1. IOC(Inversion of Control):其思想是反转资源获取的方向

    传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资。而应用了 IOC 之后,则是容器主动地将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源。这种行为也被成伪查找的被动形式。(反转控制)

  2. DI(Dependency Injection)— IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如: setter 方法)接受来自如容器的资源注入。相对于 IOC 而言,这种表述更直接。(依赖注入)

Spring 初体验

  1. 配置 bean

id 表示 bean 的标识

class 表示 bean 对象的类目录路径

property 是给 name 参数赋值 Spring

<bean id="helloWorld" class="com.spring.beans.HelloWorld">
        <property name="name" value="Spring"/>
</bean>
  1. HelloWorld.java 类
public class HelloWorld {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void hallo(){
        System.out.println("hello: " + name);
    }

}
  1. 运行主类 Main.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {

        /*HelloWorld helloWorld = new HelloWorld();
        helloWorld.setName("AZhang");*/

        //1. 创建 Spring 的 IOC 容器对象
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 从 IOC 容器中获取 bean 实例
        HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");

        //调用 hello 方法
        helloWorld.hallo();

    }

}
  1. pom.xml 文件使用 Maven 抓包
<dependencies>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.1</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
</dependencies>

Spring结构

  1. 分层

    • Web 层:SpringMVC
    • Dao 层:Spring JDBCTemplate
    • 业务层:Spring 提供的声明式事务控制
  2. 优势

    • 方便解耦,简化开发
    • AOP 编程的支持
    • 声明式事务控制
    • 方便程序的测试
    • 方便集成各种优秀的框架
    • 降低 JavaEE API 的使用难度
    • Java 源码是经典学习范例
  3. Spring 程序开发步骤

    • 导入 Spring 开发的基本包坐标
    • 创建 Bean
    • 创建 applicationContext.xml
    • 在配置文件中进行配置
    • 创建 ApplicationContext 对象 getBean
  4. 依赖注入

    Spring 框架核心 IOC 的具体实现。

  5. 数据源(连接池)作用

    • 数据源是提高程序性能出现的
    • 事先实例化数据源,初始化部分连接资源
    • 使用连接资源时从数据源中获取
    • 使用完毕后将连接资源归还给数据源

    常见的数据源:DBCP、C3P0、BoneCP、Druid

  6. 数据源开发步骤

    • 导入数据源的坐标和数据库驱动
    • 创建数据源对象
    • 设置数据源的基本连接数据
    • 使用数据源获取连接资源和归还连接资源
  7. Spring 集成 Junit 步骤

    • 导入 spring 集成 Junit 的坐标
    • 使用 @Runwith 注解替换原来的运行期
    • 使用 @ContextConfiguration 指定配置文件或配置类
    • 使用 @Autowired 注入需要测试的对象
    • 创建测试方法进行测试
  8. Spring 集成 Web 环境

    • 在 web.xml 中配置 ContextLoaderListener 监听器(导入 spring-web 坐标)
    • 使用 WebApplicationContextUtils 获得应用上下文 ApplicationContext
  9. SpringMVC 开发步骤

    • 导入 SpringMVC 包
    • 配置 SpringMVC 核心控制器 DispathcerServlet
    • 创建 Controller 类和视图页面
    • 使用注解配置 Controller 类中业务方法的映射地址
    • 配置 SpringMVC 核心文件 spring-mvc.xml (配置组件扫描)
    • 执行访问测试
  10. SpringMVC 的执行流程

    • 用户发送请求至前端控制器 DispatcherServlet
    • DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器
    • 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet
    • DispatcherServlet 调用 HandlerAdapter 处理器适配器
    • HandlerAdapter 经过适配调用具体的处理器( Controller,也叫后端控制器)
    • Controller 执行完成返回 ModelAndView
    • HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet
    • DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器
    • ViewReslover 解析后返回具体 View
    • DispatcherServlet 根据 View 进行进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet 响应用户
  11. SpringMVC 的相关组件

    • 前端控制器:DispatcherServlet
    • 处理器映射器:HandlerMapping
    • 处理器适配器:HandlerAdapter
    • 处理器:Handler
    • 视图解析器:ViewReslover
    • 视图:View
  12. SpringMVC 的注解和配置

  13. SpringMVC 的数据响应方式

    • 页面跳转:

      • 直接返回字符串

        此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转

      • 通过 ModelAndView 对象返回
    • 回写数据

      • 直接返回字符串

        将需要回写的字符串直接返回,但此时需要通过 @ResponseBody 注解告知 SpringMVC 框架,方法返回的字符串不是跳转是直接在 http 响应体中返回。

        @ResponseBody // 告知 SpringMVC 框架,该方法不进行视图跳转 直接进行数据响应

      • 返回对象或集合

        image-20220608113654146

  14. SpringMVC 获取请求数据

    • 获得基本参数

      Controller 中的业务方法的参数名称要与请求参数的 name 一致,参数值会自动映射匹配。

    • 获得 POJO 类型参数

      Controller 中的业务方法的 POJO 参数的属性名与参数的 name 一致,参数会自动映射匹配。

    • 获得数组类型参数

      Controller 中的业务方法数组名称与请求参数的 name 一致,参数会自动映射匹配。

    • 获取集合类型参数
      • 获取集合参数时,要将集合参数包装到一个 POJO 中才可以。
      • 当时用 ajax 提交时,可以指定 contentType 为 json 形式,那么在方法参数位置使用 @RequestBody 可以直接接收集合数据而无需使用 POJO 进行包装。
    • 开放静态资源访问
      <mvc:default-servlet-handler/>
    • 请求数据乱码问题

      当 post 请求时,数据会出现乱码,我们可以设置一个过滤器来进行编码的过滤。

      <!-- 配置全局过滤的 filter -->
        <filter>
          <filter-name>CharacterEncodingFilter</filter-name>
          <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
          <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
          </init-param>
        </filter>
        <filter-mapping>
          <filter-name>CharacterEncodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
        </filter-mapping>
    • 参数绑定注解 @RequestParam

      当请求的参数名称与 Controller 的业务方法参数名称不一致时,就需要通过 @RequestParam 注解显示的绑定。

      @RequestParam 的使用

      • value: 与请求参数名称
      • required: 此在指定的请求参数是否必须包括,默认是 true,提交时如果没有此参数则报错。
      • defaultValue: 当没有指定请求参数时,则使用指定的默认值赋值。
    • 获得 Restful 风格的参数

      Restful 风格的请求是使用 “url + 请求方式” 表示一次请求目的,HTTP 协议里面四个表示操作方式的动词如下:

      • GET:用于获取资源
      • POST:用于新建资源
      • PUT:用于更新资源
      • DELETE:用于删除资源

      例如:

      • /user/1 GET: 得到 id = 1 的 user
      • /user/1 DELETE: 删除 id = 1 的 user
      • /user/1 PUT: 更新 id = 1 的 user
      • /user POST: 新增 user

      上述 url 地址 /user/1 中的就是要获得的请求参数,在 SpringMVC 中可以使用占位符进行参数绑定。地址 /user/1 可以写成 /user/{id},占位符 {id} 对应的值就是1,。在业务方法中我们可以使用 @PathVariable 注解进行占位符的匹配获取工作。

    • 自定义类型转换器
      • SpringMVC 默认提供了一些常用的类型转换器,例如客户端提交的字符串转换成 int 型进行参数设置。
      • 但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

      步骤:

      • 定义转换器类实现 Converter 接口

        public class DateConverter implements Converter<String, Date> {
            @Override
            public Date convert(String dateStr) {
                // 将日期的字符串转换成日期对象 返回
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                Date date = null;
                try {
                    date = format.parse(dateStr);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                return date;
            }
        }
      • 在配置文件中声明转换器

        <!-- 声明转换器 -->
            <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
                <property name="converters">
                    <list>
                        <bean class="com.converter.DateConverter"/>
                    </list>
                </property>
            </bean>
      • 在 <annotation-driven> 中引用转换器

        <!-- mvc 的注解驱动 -->
        <mvc:annotation-driven conversion-service="conversionService"/>
    • 获得 Servlet 相关 API

      SpringMVC 支持使用原始 ServletAPI 对象作为控制器方法的参数进行注入,常用对象如下:

      • HttpServletRequest
      • HttpServletResponse
      • HttpSession
    • 获得请求头
      • @RequestHeader

        使用 @RequestHeader 可以获得请求头信息,相当于 web阶段学习的 request.getHeader(name)

        @RequestHeader 注解的属性如下:

        • value:请求头的名称
        • required:是否必须携带此请求头
      • @CookieValue

        使用 @CookieValue 可以获得指定 Cookie 的值

        @CookieValue 注解的属性如下:

        • value:指定 cookie 的名称
        • required:是否必须携带此 cookie
  15. 文件上传

    • 文件上传客户端三要素
      • 表单项 type = "file"
      • 表单提交方式是 post
      • 表单的 enctype 属性是多部分表单形式,及 enctype = "multiypart/form-data"
    • 文件上传原理
      • 当 form 表单修改为多部分表单时,request.getParameter() 将失效。
      • enctype = "application/x-www-form-urlencoded" 时,form 表单的正文内容格式是:key = value & key = value & key = value
      • 当 from 表单的 enctype 取值为 multipart/form-data 时,请求正文内容就变成多部分形式:

        image-20220614144223072

    • 单文件上传的步骤
      • 导入 fileupload 和 io 坐标

        <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.3.1</version>
        </dependency>
        <dependency>
          <groupId>commons-io</groupId>
          <artifactId>commons-io</artifactId>
          <version>2.6</version>
        </dependency>
      • 配置文件上传解析器

        <!-- 配置文件上传解析器 -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <!-- 上传文件总大小 -->
            <property name="maxUploadSize" value="500000"/>
            <!-- 上传单个文件的大小 -->
            <property name="maxUploadSizePerFile" value="500000"/>
            <!-- 上传文件的编码类型 -->
            <property name="defaultEncoding" value="UTF-8"/>
        </bean>
      • 编写文件上传代码

        public void save22(String username, MultipartFile upload) throws IOException {
            System.out.println(username);
            // 获得上传文件的名称
            String originalFilename = upload.getOriginalFilename();
            upload.transferTo(new File("D:\\SSM_2022\\Spring\\spring_mvc\\src\\main\\webapp\\docx\\" + originalFilename));
        }
    • 多文件上传
  16. JDBCTemplate 基本使用

    • JDBCTemplate 开发步骤
      • 导入 spring-jdbc 和 spring-tx 坐标

        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.3.20</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>5.3.20</version>
        </dependency>
      • 创建数据库表和实体
      • 创建 JDBCTemplate 对象

        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // 设置数据源对象 要知道数据库在哪
        jdbcTemplate.setDataSource(dataSource);
      • 执行数据库操作

        • 更新操作:

          jdbcTemplate.update(sql, params);
        • 查询操作:

          jdbcTemplate.query(sql, Mapper, params);
          jdbcTemplate.queryForObject(sql, Mapper, params);
  17. Spring 环境搭建步骤

    • 创建工程
    • 导入静态页面
    • 导入需要坐标 pom.xml
    • 创建包结构

      • controller
      • service
      • dao
      • domain
      • utils
    • 导入数据库脚本
    • 创建 POJO 类
    • 创建配置文件

      • applicationContext.xml
      • spring-mvc.xml
      • jdbc.properties
      • log4j.properties
  18. SpringMVC 拦截器 interceptor

    • interceptor 的作用

      相当于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

    • 自定义 Interceptor 步骤

      • 创建拦截器实现 HandlerInterceptor 接口
      • 配置拦截器

        <!-- 配置拦截器 -->
        <mvc:interceptors>
            <mvc:interceptor>
                <!-- 对哪些资源进行拦截操作 -->
                <mvc:mapping path="/**"/>
                <bean class="com.interceptor.MyInterceptor"/>
            </mvc:interceptor>
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <bean class="com.interceptor.MyInterceptor2"/>
            </mvc:interceptor>
        </mvc:interceptors>
      • 测试拦截器的拦截效果
  19. SpringMVC 异常处理

    • 异常处理思路

      系统中的异常包括两大类:预期异常和运行时异常( RuntimeException ),前者通过捕获异常从何获取异常信息,后者只要通过规范代码开发、测试等手段减少运行时异常的发生。

    • 两种方式

      • 使用 SpringMVC 提供的简单异常处理器 SimpleMappingExceptionResolver

        <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <property name="defaultErrorView" value="error"/>
            <property name="exceptionMappings">
                <map>
                    <entry key="java.lang.ClassCastException" value="error1"/>
                    <entry key="java.lang.ArithmeticException" value="error2"/>
                    <entry key="java.io.FileNotFoundException" value="error3"/>
                    <entry key="java.lang.NullPointerException" value="error4"/>
                    <entry key="com.exception.MyException" value="error5"/>
                </map>
            </property>
        </bean>
      • 实现 Spring 的异常处理器接口 HandlerExceptionResolver 自定义自己的异常处理器。
    • 自定义异常处理步骤

      • 创建异常处理器类实现 HandlerExceptionResolver

        public class MyExceptionResolver implements HandlerExceptionResolver {
        
            /*
                参数 Exception:异常对象
                返回值 ModelAndView:跳转到错误视图
             */
            @Override
            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                ModelAndView modelAndView = new ModelAndView();
        
                if (ex instanceof MyException) {
                    modelAndView.addObject("info", "自定义异常");
                } else if (ex instanceof ClassCastException) {
                    modelAndView.addObject("info", "类转换异常");
                } else if (ex instanceof ArithmeticException) {
                    modelAndView.addObject("info", "零异常");
                } else if (ex instanceof FileNotFoundException) {
                    modelAndView.addObject("info", "文件找不到异常");
                } else if (ex instanceof NullPointerException) {
                    modelAndView.addObject("info", "空指针异常");
                } else {
                    modelAndView.addObject("info", "通用错误异常");
                }
                modelAndView.setViewName("error");
        
                return modelAndView;
            }
        }
      • 配置异常处理器

        <bean class="com.resolver.MyExceptionResolver"/>
      • 编写异常页面
      • 测试异常跳转
  20. AOP 面向切面编程

    通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
    
    • 动态代理的优点

      在不修改源码的情况下,对目标方法进行相应的增强。

      作用:完成程序之间的松耦合。

    • AOP 作用及其优势

      作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。

      优势:减少重复代码,提高开发优势,并且便于维护。

    • 常用的动态代理技术

      • JDK 代理:基于接口的动态代理技术
      • cglib 代理:基于父类的动态代理技术
    • AOP 相关概念 (术语)

      • Target(目标对象):代理的目标对象
      • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类
      • Joinpoint(连接点):所谓连接点是指哪些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。可以被增强的方法叫做连接点
      • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpiont 进行拦截的定义。也叫切点,被增强的方法叫做切入点
      • Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。对目标方法进行增强的那个增强方法叫做增强
      • Aspect(切面):是切入点和通知的结合。目标方法+增强
      • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。切点和增强结合的过程叫做织入
    • AOP 开发明确的事项

      1. 需要编写的内容

        • 编写核心业务代码(目标类的目标方法)
        • 编写切面类,切面类中有通知(增强功能方法)
        • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
      2. AOP 技术实现的内容

        Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

      3. AOP 底层使用哪种代理方式

        在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

      4. 基于 XML 的 AOP 开发步骤

        • 导入 AOP 相关坐标

          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.20</version>
          </dependency>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
          </dependency>
        • 创建目标接口和目标类(内部有切点)
        • 创建切面类(内部有增强方法)
        • 将目标类和切面类的对象创建权交给 spring
        • 在 applicationContext.xml 中配置织入关系

          <!-- 目标对象 -->
          <bean id="target" class="com.aop.Target"/>
          
          <!-- 切面对象 -->
          <bean id="myAspect" class="com.aop.MyAspect"/>
          
          <!-- 配置织入 告诉 spring 框架 哪些方法(切点)需要进行哪些增强(前置、后置...) -->
          <aop:config>
              <!-- 声明切面 -->
              <aop:aspect ref="myAspect">
                  <!-- 切面:切点 + 通知 -->
                  <aop:before method="before" pointcut="execution(public void com.aop.Target.save())"/>
              </aop:aspect>
          </aop:config>

          切点表达式的写法:

          切点表达式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

          • 访问修饰符可以省略
          • 返回值类型、包名、类名、方法名可以使用 * 代表任意
          • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
          • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

          通知的类型:

          通知的配置语法:

          <aop:通知类型 method="切面类中的方法名" pointcut="切点表达式"></aop:通知类型>
          • 前置通知

            <aop:before>
            用于配置前置通知,指定增强方法在切点方法之前执行
          • 后置通知

            <aop:after-returning>
            用于配置后置通知,指定增强方法在切点方法之后执行
          • 环绕通知

            <aop:around>
            用于配置环绕通知,指定增强方法在切点方法之前和之后都执行
          • 异常抛出通知

            <aop:after-throwing>
            用于配置异常抛出通知,指定增强方的法在出现异常时执行
          • 最终通知

            <aop:after>
            用于配置最终通知,无论增强方式执行是否有异常都会执行

          切点表达式的抽取:

          当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

          <aop:pointcut id="myPointcut" expression="execution(* com.aop.*.*(..))"/>
          <aop:around method="around" pointcut-ref="myPointcut"/>
          <aop:after method="after" pointcut-ref="myPointcut"/>
        • 测试代码
      5. 基于注解的 AOP 开发步骤

        • 创建目标接口和目标类(内部有切点)
        • 创建切面类(内部有增强方法)
        • 将目标类和切面类的对象创建权交给 spring
        • 在切面类中使用注解配置织入关系
        • 在配置文件中开启组件扫描和 AOP 的自动代理

          <!-- Aop 自动代理 -->
          <aop:aspectj-autoproxy/>
        • 测试

        切点表达式的抽取:

        // 抽取
        @Pointcut("execution(* com.anno.*.*(..))")
        public void pointcut() {}
        
        // 两种方式使用:
        // 1.
        @Before("pointcut()")
        public void before() {
            System.out.println("前置增强。。。");
        }
        // 2.
        @AfterReturning("MyAspect.pointcut()")
        public void afterReturning() {
            System.out.println("后置增强。。。");
        }
  21. Spring 事务控制

    • 编程式事务控制三大对象

      • PlatformTransactionManager

        一个接口 根据不同的 Dao 层的实现来使用改接口对应的实现类

      • TransactionDefinition

        事务的定义对象

        1. 事务的隔离级别

          设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

          • ISOLATION_DEFAULT:当前数据库默认的
          • ISOLATION_READ_UNCOMMITTED:读-未提交的,上述三种问题都不能解决
          • ISOLATION_READ_COMMITTED:读-已提交的,可解决脏读
          • ISOLATION_REPEATABLE_READ:可重复读,解决了不可重复读
          • ISOLATION_SERIALIZABLE:串行化,所有问题都能解决,但是性能很低
        2. 事务的传播行为

          image-20220630012149017

      • TransactionStatus

        事务的状态信息

    • 基于 XML 的声明式事务控制

      配置要点:

      • 平台事务管理器配置:

        <!-- 配置平台事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
      • 事务通知的配置:

        <!-- 通知 事务的增强 -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!-- 设置事务的属性信息 -->
            <tx:attributes>
                <tx:method name="*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"/>
            </tx:attributes>
        </tx:advice>
      • 事务 AOP 织入的配置:

        <!-- 配置事务的 aop 织入 -->
        <aop:config>
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service.impl.*.*(..))"/>
        </aop:config>
    • 基于注解的声明式事务控制

      1. 使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如:隔离级别,传播行为等。
      2. 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
      3. 使用在方法上,不同的方法可以采用不同的事务参数配置。
      4. Xml 配置文件中要开启事务的注解驱动

        <!-- 事务的注解驱动 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
        @Service("accountService")
        @Transactional(isolation = Isolation.REPEATABLE_READ)
        public class AccountServiceImpl implements AccountService {
        
            @Autowired
            private AccountDao accountDao;
        
            @Override
            @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
            public void transfer(String outMan, String inMan, double money) {
                accountDao.out(outMan, money);
        //        int i = 1/0;
                accountDao.in(inMan, money);
            }
        }

        配置要点:

        • 平台事务管理器配置(xml 方式)
        • 事务通知的配置(@Transactional 注解配置)
        • 事务注解驱动的配置

          <tx:annotation-driven/>

Vue

Hallo Vue

<body>
    <div id="app">{{message}}</div>
</body>
<script>
    //let(定义变量)/const(定义常量)
    const app = new Vue({
        el: '#app',    //用于挂载要管理的元素
        data: {        //定义数据
            message: 'Hello Vue!'
        }
    })
</script>

vue的列表展示(v-for)

<body>
    <div id="app">
        <ul>
            <li v-for="item in movies">
                {{item}}
            </li>
        </ul>
    </div>
</body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            movies: ['星际穿越', '盗梦空间', '大话西游']
        }
    })
    app.movies.push('闻香识女人');
</script>

实现:

  • 星际穿越
  • 盗梦空间
  • 大话西游
  • 闻香识女人

计数器(v-on监听,"v-on:"可用@代替称为语法糖)

  1. <body>
        <div id="app">
            <h2>当前计数:{{counter}}</h2>
            <button @click="counter++">+</button>
            <button @click="counter--">-</button>
        </div>
    </body>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                counter: 0
            }
        })
    </script>
  2. <body>
        <div id="app">
            <h2>当前计数:{{counter}}</h2>
            <!--<button v-on:click="counter++">+</button>
            <button v-on:click="counter--">-</button>-->
            <button @click="add">+</button>
            <button @click="sub">-</button>
        </div>
    </body>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                counter: 0
            },
            methods: {
                add: function(){
                    this.conter++
                },
                sub: function(){
                    this.conter--
                }
            }
        })
    </script>

02-插值操作

插值操作-mustache语法({{massage}})

<body>
    <h2>{{message}}</h2>
    <h2>{{message}},ABC</h2>
    <!--在mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式-->
    <h2>{{firstname + lastname}}</h2>
    <h2>{{firstname + ' ' + lastname}}</h2>
    <h2>{{firstname}} {{lastname}}</h2>
    <h2>{{counter * 2}}</h2>
</body>
<script>
    //let(定义变量)/const(定义常量)
    const app = new Vue({
        el: '#app',    //用于挂载要管理的元素
        data: {        //定义数据
            message: 'Hello Vue!',
            firstname: 'kobe',
            lastname: 'bryant',
            counter: 100
        }
    })
</script>

v-once——只执行一次

  1. 该指令后面不需要跟任何表达式
  2. 该指令表示元素和组件只渲染一次,不会随着数据的改变而改变
<body>
    <div id="app">
        <h2 v-once>{{massage}}</h2>
        <!--massage改变时,界面并没有重新渲染-->
    </div>
</body>

v-html——解析html指令

  1. 该指令后面往往会跟上一个string类型
  2. 会将string的html解析出来并且进行渲染
<body>
    <div id="app">
        <h2 v-html="url"></h2>
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                url: '<a href="http://www.baidu.com">百度一下</a>'
            }
        })
    </script>
</body>

v-text——与mustache相似

  1. 接受一个string类型
  2. 覆盖当前文本
<body>
    <div id="app">
        <h2 v-text="message"></h2>
        <!--等于<h2>{{message}}</h2>-->
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                message: 'Azhang'
            }
        })
    </script>
</body>

v-pre——用于跳过这个元素和它子元素的编译过程,用于显示原本的mustache语法

<body>
    <div id="app">
        <h2 v-pre>{{message}}</h2>
        <!--直接显示'{{message}}'-->
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                message: 'Azhang'
            }
        })
    </script>
</body>

v-cloak

<style>
    [v-cloak]{
        display: none;
    }
    //给带有v-cloak属性的元素附上该css属性
</style>
<body>
    <div id="app" v-cloak>
        {{message}}
        <!--1s后显示'Azhang'-->
    </div>
    
    <script>
        //在vue解析之前,div中有一个属性叫v-cloak
        //在vue解析之后,div中没有一个属性叫v-cloak
        steTimeout(function(){
            const app = new vue({
                el: '#app',
                data: {
                    message: 'Azhang'
                }
            })
        }, 1000)
    </script>
</body>

03-动态绑定属性

v-bind——动态绑定属性

<body>
    <div id="app">
        <img v-bind:src="imgURL" alt="">
        <a v-bind:href="aHref">百度一下</a>
        <!--语法糖“:” <img :src="imgURL" alt="">
        <a :href="aHref">百度一下</a>-->
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                imgURL: '图片地址',
                aHref: 'http://www.baidu.com'
            }
        })
    </script>
</body>

v-bind——动态绑定class(对象语法)

<style>
    .active{
        color: red;
    }
</style>
<body>
    <div id="app">
        用法1:
        <!--<h2 class="active">{{message}}</h2>-->
        <!--<h2 :class="active">{{message}}</h2>-->
        
        用法2:
        <!--<h2 v-bind:class="{key1: value1, key2: value2}">{{message}}</h2>-->
        <!--<h2 v-bind:class="{类名1: Boolean, 类名2: Boolean}">{{message}}</h2>-->
        <!--当Boolean值为true时,该类名会被赋上该标签的class属性-->
        <!--<h2 v-bind:class="{active: isActive, line: isLine}">{{message}}</h2>-->
        
        用法3:
        <!--<h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2>-->
        <!--不冲突 直接合并-->
        
        用法4:
        <h2 v-bind:class="getClasses()">{{message}}</h2>
        <button v-on:click="btnClick">按钮</button>
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                message: 'Azhang',
                isActive: true,
                isLine: true
            },
            methods: {
                btnClick: function(){
                    this.isActive = !this.isActive
                },
                getClasses: function(){
                    return {active: this.isActive, line: this.isLine}
                }
            }
        })
    </script>
</body>

v-bind——动态绑定class(数组语法) ’用的少‘

<body>
    <div id="app">
        <!--<h2 v-bind:class="['active', 'line']">{{message}}</h2>-->
        <h2 v-bind:class="[active, line]">{{message}}</h2>
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                message: 'Azhang',
                active: 'aaaaa',
                line: 'bbbbbb'
            }
        })
    </script>
</body>

v-bind——动态绑定style(对象语法)

<body>
    <div id="app">
        <!--<h2 v-bind:style="{fontSize: '50px'}">{{message}}</h2>-->
        <!--css属性名需用驼峰表示法或者使用font-size表示,属性值若不是变量需用单引号括住-->
        <!--<h2 v-bind:style="{fontSize: finalSize}">{{message}}</h2>-->
        <!--<h2 v-bind:style="{fontSize: finalSize + 'px'}">{{message}}</h2>-->
        <!--<h2 v-bind:style="{fontSize: finalSize + 'px', backgroundColor: finalColor}">{{message}}</h2>-->
        <h2 v-bind:style="getStyles()">{{message}}</h2>
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                message: 'Azhang',
               // finalSize: '100px'
                finalSize: 100,
                finalColor: 'red'
            },
            methods: {
                getStyles: function(){
                return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor}
                }
            }
        })
    </script>
</body>

v-bind——动态绑定style(数组语法)'用的少'

<body>
    <div id="app">
        <h2 v-bind:style="[baseStyle, baseStyle1]">{{message}}</h2>
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                message: 'Azhang',
                baseStyle: {backgroundColor: 'red'},
                baseStyle1: {fontSize: '100px'}
            }
        })
    </script>
</body>

04-计算属性

计算属性的基本使用

<body>
    <div id="app">
        <h2>{{firstName + ' ' + lastName}}</h2>
        <h2>{{firstName}} {{lastName}}</h2>
        <h2>{{getFullName()}}</h2>
        <h2>{{fullName}}</h2>
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                firstName: 'Lebron',
                lastName: 'James'
            },
            computed: {
                fullName: function(){
                    return this.firstName + ' ' + this.lastName
                }
            },
            methods: {
                getFullName(){
                    return this.firstName + ' ' + this.lastName
                }
            }
        })
    </script>
</body>

计算属性的复杂操作

<body>
    <div id="app">
        <h2>总价格:{{totalPrice}}</h2>    
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                books: [
                    {id: 110, name: 'unix编程艺术', price: 119},
                    {id: 111, name: '代码大全', price: 105},
                    {id: 112, name: '深入理解计算机原理', price: 98},
                    {id: 113, name: '现代操作系统', price: 87}
                ]
            },
            computed: {
                totalPrice: function(){
                    let result = 0
                    for(let i=0, i<this.books.length; i++){
                        result += books[i].price
                    }
                    return result
                    
                    //for(let i in this.books){
                    //    this.books[i]
                    //}
                    //for(let book of this.books){
                    //    
                    //}
                }
            }
        })
    </script>
</body>

计算属性的setter和getter

<body>
    <div id="app">
        <h2>总价格:{{totalPrice}}</h2>    
    </div>
    
    <script>
        const app = new vue({
            el: '#app',
            data: {
                firstName: 'kobe',
                lastName: 'bryant'
            },
            computed: {
                //fullName: function(){
                //    return this.firstName + ' ' + this.lastName
                //}
                
                //本质上是这样的:
                fullName: {
                    //set: function(){
                    //
                    //}
                    get: function(){
                        return this.firstName + ' ' + this.lastName
                    }
                }
                //但是一般情况下没有set方法,所以get也可以直接省略写成之前那样
                //set方法使用时需要有参数
                fullName: {
                    set: function(newValue){
                    //这个newValue是该计算属性被调用时所传进来的参数
                    }
                    get: function(){
                        return this.firstName + ' ' + this.lastName
                    }
                }
            }
        })
    </script>
</body>

Cookie

一、简介

  1. 某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。
  2. 通过 Cookie 和 Session 技术来实现记录访问者的一些基本信息 。
  3. Cookie 在计算机中是个存储在浏览器目录中的文本文件 。

二、特点

  • 在同一个页面中设置 Cookie,实际上是按从后往前的顺序进行的。如果要先删除一个 Cookie,再写入一个 Cookie,则必须先写写入语句,再写删除语句,否则会出现错误 。
  • Cookie是面向路径的。缺省路径 (path) 属性时,Web 服务器页会自动传递当前路径给浏览器,指定路径强制服务器使用设置的路径。在一个目录页面里设置的 Cookie 在另一个目录的页面里是看不到的 。
  • Cookie 必须在 HTML 文件的内容输出之前设置;不同的浏览器 (Netscape Navigator、Internet Explorer) 对 Cookie 的处理不一致,使用时一定要考虑;客户端用户如果设置禁止 Cookie,则 Cookie 不能建立。 并且在客户端,一个浏览器能创建的 Cookie 数量最多为 300 个,并且每个不能超过 4KB,每个 Web 站点能设置的 Cookie 总数不能超过 20 个 。

三、源码

package javax.servlet.http;

import java.text.MessageFormat;
import java.util.ResourceBundle;

public class Cookie implements Cloneable {
    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
    private String name;
    private String value;
    private String comment;
    private String domain;
    private int maxAge = -1;
    private String path;
    private boolean secure;
    private int version = 0;
    private static final String tspecials = ",; ";

    public Cookie(String name, String value) {
        if (this.isToken(name) && !name.equalsIgnoreCase("Comment") && !name.equalsIgnoreCase("Discard") && !name.equalsIgnoreCase("Domain") && !name.equalsIgnoreCase("Expires") && !name.equalsIgnoreCase("Max-Age") && !name.equalsIgnoreCase("Path") && !name.equalsIgnoreCase("Secure") && !name.equalsIgnoreCase("Version") && !name.startsWith("$")) {
            this.name = name;
            this.value = value;
        } else {
            String errMsg = lStrings.getString("err.cookie_name_is_token");
            Object[] errArgs = new Object[]{name};
            errMsg = MessageFormat.format(errMsg, errArgs);
            throw new IllegalArgumentException(errMsg);
        }
    }

    public void setComment(String purpose) {
        this.comment = purpose;
    }

    public String getComment() {
        return this.comment;
    }

    public void setDomain(String pattern) {
        this.domain = pattern.toLowerCase();
    }

    public String getDomain() {
        return this.domain;
    }

    public void setMaxAge(int expiry) {
        this.maxAge = expiry;
    }

    public int getMaxAge() {
        return this.maxAge;
    }

    public void setPath(String uri) {
        this.path = uri;
    }

    public String getPath() {
        return this.path;
    }

    public void setSecure(boolean flag) {
        this.secure = flag;
    }

    public boolean getSecure() {
        return this.secure;
    }

    public String getName() {
        return this.name;
    }

    public void setValue(String newValue) {
        this.value = newValue;
    }

    public String getValue() {
        return this.value;
    }

    public int getVersion() {
        return this.version;
    }

    public void setVersion(int v) {
        this.version = v;
    }

    private boolean isToken(String value) {
        int len = value.length();

        for(int i = 0; i < len; ++i) {
            char c = value.charAt(i);
            if (c < ' ' || c >= 127 || ",; ".indexOf(c) != -1) {
                return false;
            }
        }

        return true;
    }

    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException var2) {
            throw new RuntimeException(var2.getMessage());
        }
    }
}

四、Cookie的使用

         Cookie cookie = new Cookie("username","password"); // 新建Cookie
        cookie.setMaxAge(Integer.MAX_VALUE); // 设置生命周期为MAX_VALUE
        response.addCookie(cookie); // 输出到客户端

五、修改

只有添加方法,无修改方法,若要修改, 则只能再次生成一次,用后来的数据覆盖前面的数据(前提是key值必须相同)。

六、删除

也没有删除方法, 若要想实现删除Cookie的操作,那么我们需要将Cookie的maxAge参数置为负数,比如说源码中的初始值就是-1,所以说我们也可以参考这个,如果要想将Cookie删除,我们也可以将其maxAge参数置为-1。

七、有效期

maxAge是Cookie的有效期,默认的情况下,Cookie的初始值是-1,其意思就是当浏览器退出时,清除Cookie。如果我们要想让Cookie保存一段时间,比如说是一周,其设置为72460*60(秒)。

八、注意:

修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。

九、域名

  • Cookie是不可跨域名的。
  • 正常情况下,同一个一级域名下的两个二级域名如www.helloweenvsfei.comimages.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有helloweenvsfei.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数为“.helloweenvsfei.com”。

十、校验

根据密码校验

根据Cookie中的用户名在数据库中查找,一旦找到该信息,立即将其取出,然后再与Cookie中的密码进行比较,如果一致,则验证通过。

/**
     * 根据cookie中的密码进行校验
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/password")
    public String password(HttpServletRequest request, HttpServletResponse response){
        Cookie[] cookies = request.getCookies();
        if(cookies.length > 0){
            for(int i = 0;i < cookies.length;i ++){
                Cookie cookie = cookies[i];
                log.info("cookie的key为:{},value为:{}",cookie.getName(),cookie.getValue());
                //验证登录信息
                if(validParamPassword(cookie)){
                    return "success";
                }
            }
        }
        return "error";
    }

    /**
     * 根据cookie中的密码进行校验
     * @param cookie
     * @return
     */
    private Boolean validParamPassword(Cookie cookie){
        if(cookie.getName().equals("zhangsan") && cookie.getValue().equals(MD5Util.encrypt("zhangsan123"))){
            return true;
        }
        if(cookie.getName().equals("lisi") && cookie.getValue().equals(MD5Util.encrypt("lisi123"))){
            return true;
        }
        return false;
    }

根据最后一次Cookie时间进行校验

该方法的思路是每次服务器向用户颁发Cookie时都在数据库中更新一次Cookie颁发时间,这样如果下次访问,服务器可通过Cookie中的用户名在数据库中查出相应的信息,然后再与客户端的颁发时间进行比对,如果颁发时间一致,则验证通过,该方法的另一个优点是避免了用户密码存储在用户本地,因而更加的安全可靠。

/**
     * 根据cookie中的存储时间进行校验
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/cookiedTime")
    public String cookiedTime(HttpServletRequest request, HttpServletResponse response){
        Cookie[] cookies = request.getCookies();
        if(cookies.length > 0){
            for(int i = 0;i < cookies.length;i ++){
                Cookie cookie = cookies[i];
                log.info("cookie的key为:{},value为:{}。",cookie.getName(),cookie.getValue());
                //验证登录信息
                if(validParamTime(cookie)){
                    return "success";
                }
            }
        }
        return "error";
    }

    /**
     * 根据cookie存储时间进行校验
     * @param cookie
     * @return
     */
    private Boolean validParamTime(Cookie cookie){
        Test test = cookieService.selectByName(cookie.getName());
        if(test != null){
            if(String.valueOf(test.getUpdatetime().getTime()).equals(cookie.getValue())){
                return true;
            }
        }
        return false;
    }

限定符

  1. ?

    限定符,代表前面的字符需要出现0次或者1次 可以说成前面字符可有可无

    image-20220818170105038

  2. *

    匹配0个或者多个字符

    image-20220818170143271

  3. +

    匹配出现1次以上的字符

    image-20220818170242118

  4. {...}

    精确匹配

    ​ b出现6次

    image-20220818170357936

    ​ 可出现范围

    image-20220818170446840

    ​ 不设置上限

    image-20220818170558565

  5. 多个字符

    匹配多个字符

    image-20220818170735541

“或”运算

image-20220818170900774

字符型

方括号里的内容代表要求匹配的字符只能取自于它们

image-20220818171101737

还可以指定范围

a-z 代表所有小写的英文字符

image-20220818171217263

a-zA-Z 代表所有大写的英文字符

image-20220818171258183

a-zA-Z0-9代表所有的大小写英文字符和数字

image-20220818171321414

^ 字符代表除 ^ 后面列出的以外的字符

1 代表所有的非数字字符 包括换行符

image-20220818171423943

元字符

  1. /d 代表数字字符
  2. /w 代表单词字符(英文字符 数字 下划线)
  3. /s 代表空白符 同时包含 tab 字符以及换行符
  4. /D 代表非数字字符
  5. /W 代表非单词字符
  6. /S 代表非空白字符
  7. .(英文句号) 代表任意字符 但不包含换行符
  8. ^ 匹配行首 $ 匹配行尾

    如 ^a 只会匹配行首的 a

    image-20220818172125511

    ​ a$ 只会去匹配行尾的 a

贪婪与懒惰匹配

贪婪匹配

image-20220818172436729

懒惰匹配

image-20220818172453733

实例

  1. RGB 颜色值匹配

    image-20220818172715880

  2. IPv4 地址匹配

    image-20220818173242590

总结

image-20220818173324635


  1. 0-9

Java与MySQL的连接

1.引用sql的操作库

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

你还需要导入一个叫做mysql-connector-java-5.1.44-bin的jar,记住一定要tomcat的lib目录也来一份

2.加载驱动

public static void main(String[] args) throws Exception{
        //1.初始化jdbc驱动器 注意一定要在方法中 throws Exception
        Class.forName("com.mysql.jdbc.Driver");//推荐使用这种方式来加载驱动
...

3.数据库的连接

//要连接的数据库URL
String url = "jdbc:mysql://localhost:3306/jdbcStudy";
//连接的数据库时使用的用户名
String username = "root";
//连接的数据库时使用的密码
String password = "123";

Connection conn = DriverManager.getConnection([url], [username], [password]);

4.获取用于向数据库发送指令的statement(声明)

Statement st = conn.createStatement();//获取对象

5.向数据库发送sql语句并获取代表结果集的resultset

String sql = "select id,name,password,email,birthday from users";//定义命令

ResultSet rs = st.executeQuery(sql);//发送命令并获取返回

statement中execute、executeQuery和executeUpdate的区别

executeQuery

用于产生单个结果集(ResultSet)的语句,例如 SELECT 语句

只能执行查询语句,执行后返回代表查询结果的ResultSet对象。

executeUpdate

用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句

返回值是一个整数(int),指示受影响的行数(即更新计数)。

对于 CREATE TABLE 或 DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。

execute

可用于执行任何SQL语句,返回一个boolean值,表明执行该SQL语句是否返回了ResultSet

6.取出数据

while(rs.next()){
    System.out.println("id=" + rs.getObject("id"));
    System.out.println("name=" + rs.getObject("name"));
    System.out.println("password=" + rs.getObject("password"));
    System.out.println("email=" + rs.getObject("email"));
    System.out.println("birthday=" + rs.getObject("birthday"));
}

7.关闭连接

rs.close();
st.close();
conn.close();

完整的代码

package program1;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class SqlTest{

    public static void main(String[] args) throws Exception{
        //1.初始化jdbc驱动器 注意一定要在方法中 throws Exception
        Class.forName("com.mysql.jdbc.Driver");
        
        //2.连接数据库
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306", "root", "a112233");
        
        
        //3.获取用于向数据库发送指令的statement(声明)
        Statement st = conn.createStatement();//获取对象
        
        //4.换库
        st.execute("USE test");
        
        //5.数据库发送sql语句并获取代表结果集的resultset
        String sql = "SELECT * FROM test1";//定义命令
        ResultSet rs = st.executeQuery(sql);//发送命令并获取返回
        
        //7.取出数据
        while(rs.next()){
            System.out.println("text=" + rs.getObject("text"));
            System.out.println("a1=" + rs.getObject("a1"));
        }
        
        
        //8.更新数据
        sql = "UPDATE test1 SET text=\""+System.currentTimeMillis()+"\" WHERE a1=1";//获取时间戳并写到text中
        int rsi = st.executeUpdate(sql);
        System.out.println("data update "+rsi);
        
        //9.重新查看数据
        rs = st.executeQuery("SELECT * FROM test1");
        while(rs.next()){
            System.out.println("text=" + rs.getObject("text"));
            System.out.println("a1=" + rs.getObject("a1"));
        }
        
        //last.关闭连接
        rs.close();
        st.close();
        conn.close();
        
        System.out.println("program end");
    }
}

各种类的讲解

DriverManager 类讲解

Jdbc程序中的DriverManager用于加载驱动,并创建与数据库的链接,这个API的常用方法:

  1. DriverManager.registerDriver(new Driver())
  2. DriverManager.getConnection(url,user, password),

注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:

1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。

2、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。

推荐方式:Class.forName("com.mysql.jdbc.Driver");

采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。

数据库URL讲解

URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:

![jdbc:mysql: C] //localhost:3306/test ](file:///C:/Users/jmche/AppData/Local/Packages/Microsoft.Office.OneNote_8wekyb3d8bbwe/TempState/msohtmlclip/clip_image001.png)

常用数据库URL地址的写法:

  • Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
  • SqlServer写法:jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
  • MySql写法:jdbc:mysql://localhost:3306/sid

如果连接的是本地的Mysql数据库,并且连接使用的端口是3306,那么的url地址可以简写为: jdbc:mysql:///数据库

Connection类讲解

Jdbc程序中的Connection,它用于代表数据库的链接,Collection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:

  • createStatement():创建向数据库发送sql的statement对象。
  • prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
  • prepareCall(sql):创建执行存储过程的callableStatement对象。
  • setAutoCommit(boolean autoCommit):设置事务是否自动提交。
  • commit() :在链接上提交事务。
  • rollback() :在此链接上回滚事务。

Statement类讲解

Jdbc程序中的Statement对象用于向数据库发送SQL语句, Statement对象常用方法:

  • executeQuery(String sql) :用于向数据发送查询语句。
  • executeUpdate(String sql):用于向数据库发送insert、update或delete语句
  • execute(String sql):用于向数据库发送任意sql语句
  • addBatch(String sql) :把多条sql语句放到一个批处理中。
  • executeBatch():向数据库发送一批sql语句执行。

ResultSet类讲解

Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。

ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:

获取任意类型的数据

getObject(int index)

getObject(string columnName)

获取指定类型的数据,例如:

getString(int index)

getString(String columnName)

ResultSet还提供了对结果集进行滚动的方法:

  • next():移动到下一行
  • Previous():移动到前一行
  • absolute(int row):移动到指定行
  • beforeFirst():移动resultSet的最前面。
  • afterLast() :移动到resultSet的最后面。

释放资源

Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象,特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。