尚硅谷Spring基础 笔记

Spring基础笔记

【1】Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 使用xsd来约束XML配置文件的写法,是在根标签上写 xmlns相关配置。
         xml name space 名字空间。
         xmlns:xsi - 一般都是固定的,不需要改变的。
         xsi:schemaLocation - 当前的XML配置文件,使用哪一个xsd文件来约束。内容是
          唯一的名称(一般是一个http地址)   这个xsd文件的在线访问地址
     -->
</beans>

【2】Spring IoC/DI

IoC(Inversion of Control)中文名称:控制反转。也被称为DI(dependency injection )依赖注入。属于同一件事情的两个名称。
IoC/DI是指一个过程:对象的创建仅仅通过Spring容器负责,Spring容器可以通过对象的构造方法或工厂方法进行实例化对象。在创建对象过程中,如果对象需要依赖其他对象,也可以直接在Spring容器中注入到当前对象。
整个过程中对象本身在容器中控制自己的实例化(所以叫做控制反转),通过构造方法或setter方法把依赖对象注入到自己(所以又叫做依赖注入)

【3】容器

容器(Container):放置所有管理对象的对象。其本质是在容器对象里面有一个全局Map对象,map对象中放置所有被管理的对象。Spring中容器是指ApplicationContext接口及子接口或实现类。

【3】Beans

容器中所有被管理的对象称为beans。如果单说其中一个对象可称为bean

【4】Bean实例化

有两种方式
方式一:
通过构造方法进行实例化。默认使用无参构造。在XML中通过<bean>的class属性指定类的全限定路径,然后就可以实例化对象。
注意:无参构造方法如果不存在,将抛出异常BeanCreationException
方式二:
通过工厂进行实例化。可以通过静态工厂和实例工厂进行实例化

如下所示:工厂的创建方法属于构造方法进行实例化,Student.class 的实例化是通过工厂模式进行的实例化
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 工厂创建bean对象 -->
    <!-- 静态工厂
         不需要创建工厂对象。直接使用工厂的静态方法,创建产品对象
         class - 定义静态工厂类型
         factory-method - 静态工厂中的工厂方法名
     -->
    <bean id="staticFactoryStudent" class="com.bjsxt.factory.StudentStaticFactory" factory-method="newInstance"></bean>

    <!-- 实例工厂
         先让spring容器创建一个工厂的bean对象。
         再基于这个工厂的bean对象,创建产品(学生)bean对象。
     -->
    <bean id="stuInstanceFactory" class="com.bjsxt.factory.StudentInstanceFactory"></bean>
    <!-- 基于工厂bean对象,创建产品对象
         factory-bean - 使用的工厂bean对象的唯一标记,可以是id,可以是name
         factory-method - 工厂bean对象中的工厂方法名。
     -->
    <bean id="instanceFactoryStudent" factory-bean="stuInstanceFactory" factory-method="getInstance"></bean>
    
</beans>
package com.bjsxt.factory;

import com.bjsxt.pojo.Student;

/**
 * 学生实例工厂
 *  这个工厂,必须先创建对象,再调用方法,才能创建学生对象。
 */
public class StudentInstanceFactory {
    private Student student = new Student();

    public StudentInstanceFactory(){
        System.out.println("创建了学生实例工厂对象。");
    }

    /**
     * 工厂方法。调用即返回对象student。
     * @return
     */
    public Student getInstance(){
        return student;
    }
}

package com.bjsxt.factory;

import com.bjsxt.pojo.Student;

/**
 * 静态工厂。
 *  当前类型不需要创建对象。直接使用静态方法,创建产品对象
 */
public class StudentStaticFactory {
    private static Student student = new Student();
    public StudentStaticFactory(){
        System.out.println("学生静态工厂创建对象");
    }
    public static Student newInstance(){
        // 静态工厂方法
        return student;
    }
}
    @Test
    public void testStaticFactory(){
        ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("staticFactoryStudent", Student.class);
        System.out.println(student);
    }

    @Test
    public void testInstanceFactory(){
        // classpath: - 代表从classpath下开始查找
        ApplicationContext context =
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        // 获取工厂创建的对象
        Student student = context.getBean("instanceFactoryStudent", Student.class);
        System.out.println(student);
    }

【5】属性注入

有两种方式
方式一:
构造注入(Constructor-based Dependency Injection):通过构造方法给bean的属性赋值。所以要求bean的类中必须提供对应参数的构造方法
方式二:
设值注入,又称setter注入(Setter-based Dependency Injection):通过Bean的setter方法赋值。所以要求Bean中属性必须提供setter方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 使用xsd来约束XML配置文件的写法,是在根标签上写 xmlns相关配置。
         xml name space 名字空间。
         xmlns:xsi - 一般都是固定的,不需要改变的。
         xsi:schemaLocation - 当前的XML配置文件,使用哪一个xsd文件来约束。内容是
          唯一的名称(一般是一个http地址)   这个xsd文件的在线访问地址
     -->
    <!-- 构造注入 -->
    <bean id="student" class="com.bjsxt.pojo.Student">
        <!-- constructor-arg 就是构造参数的标签。有若干属性
             name 构造方法参数名
             value 构造方法参数值
             type 构造方法参数类型
             index 构造方法参数下标,从0开始
             ref 构造方法参数值,使用其他的bean对象的id赋值
         -->
        <constructor-arg name="id" value="10"></constructor-arg>
        <constructor-arg type="java.lang.String" value="李四"></constructor-arg>
        <constructor-arg index="2" value="30"></constructor-arg>
    </bean>
    
    <!-- 设值注入 setter注入 -->
    <bean id="student" class="com.bjsxt.pojo.Student">
        <!-- spring容器使用设置注入时,是基于property实现的。必须有getter方法。且属性命名是property名
             property - 代表调用一个setter方法。
             属性:
              name - 属性名
              value - 要赋予的属性值
              ref - 要赋予的属性值,引用其他bean的id。
         -->
        <property name="id" value="100"></property>
        <property name="name" value="王五"></property>
        <property name="age" value="25"></property>
    </bean>
</beans>
public class People {
    private int id;
    private String name;
    private int age
    // 无参数构造
    public People() {
    }
    // 有参构造
    public People(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    // getter/setter 没有粘贴到文档中
}
    @Test
    public void testConstructor(){
        ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println(student);
    }

【6】不同属性类型注入方式

  • Set
<bean id="peo7" class="com.bjsxt.pojo.People">
        <property name="hover">
            <set>
                <value>看尚学堂视频</value>
                <value>听尚学堂老师讲课</value>
            </set>
        </property>
    </bean>
  • List
    <bean id="peo7" class="com.bjsxt.pojo.People">
        <property name="subjects">
            <list>
                <value>java</value>
                <value>前端</value>
            </list>
        </property>
    </bean>
  • Array
    <bean id="peo7" class="com.bjsxt.pojo.People">
        <property name="teachers">
            <array>
                <value>高淇</value>
                <value>张佳明</value>
            </array>
        </property>
    </bean>
  • Map
    <bean id="peo7" class="com.bjsxt.pojo.People">
        <property name="phone">
            <map>
                <entry key="姓名" value="18612345678"></entry>
            </map>
        </property>
    </bean>
  • Properties
	<bean id="peo7" class="com.bjsxt.pojo.People">
        <property name="info">
            <props>
                <prop key="name"></prop>
            </props>
        </property>
    </bean>
  • Null
    <bean id="peo7" class="com.bjsxt.pojo.People">
        <property name="phone">
            <null></null>
        </property>
    </bean>
    @Test
    public void testConstructor(){
        ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println(student);
    }

【7】自动注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
        default-autowire="default" >
    
    <!-- Spring容器的自动装配。
         被Spring容器管理的若干个Bean对象,如果对象之间有关系,可以实现自动装配。
         自动装配:按照某种指定的规则,自动把容器种管理的bean对象,做赋值注入。
         自动装配规则配置: 可以有两个位置,分别是根标签的beans属性和bean标签的属性。
         根标签的属性配置是全局自动装配,bean标签的是局部自动装配。都配置,局部优先。

         根标签beans属性: default-autowire,可选值有5个。默认是default
           default - 默认值,代表不自动装配。
           no - 不自动装配。
           byType - 按照bean对象中的属性类型,从容器中找同类型的bean对象,自动注入。如果同类型对象有多个,抛出异常
           byName - 按照bean对象中的属性名,从容器中找id|name与属性名相同的bean对象,自动注入。如果同名bean的类型不匹配,抛出异常
           constructor - 按照bean对象的构造方法,从容器中找构造参数同类型或同名称的bean对象,自动注入.
              先byType,如果同类型的bean有多个,再byName,如果没有同名的,则不使用构造方法自动装配。

          bean标签属性: autowire,当前bean对象的自动装配方案,局部优先。默认是default
            default - 使用beans标签的default-autowire属性配置的自动装配方案。
            no - 不自动装配。
            byName,byType,constructor - 都和beans属性的含义完全相同。
     -->

    <bean id="people" class="com.bjsxt.pojo.People" autowire="no">
        <property name="id" value="10"/>
        <property name="name" value="张三"/>
       <!-- <property name="idCard" ref="idCard"/>-->
    </bean>

    <bean id="idCard" class="com.bjsxt.pojo.IdCard">
        <property name="id" value="1"/>
        <property name="idNo" value="220502200201011111"/>
        <property name="address" value="赛蒂工业园"/>
    </bean>

    <bean id="card" class="com.bjsxt.pojo.IdCard">
        <property name="id" value="2"/>
        <property name="idNo" value="220502200201011112"/>
        <property name="address" value="赛蒂工业园2"/>
    </bean>

</beans>
package com.bjsxt.pojo;

import java.io.Serializable;
import java.util.Objects;

public class People implements Serializable {
    private Integer id;
    private String name;
    private IdCard idCard;

    public People() {
        System.out.println("无参数构造方法");
    }

    public People(IdCard idCard) {
        System.out.println("(IdCard idCard)参数构造方法");
        this.idCard = idCard;
    }

    public People(Integer id, String name, IdCard idCard) {
        System.out.println("(Integer id, String name, IdCard idCard) 参数构造方法");
        this.id = id;
        this.name = name;
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "People{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", idCard=" + idCard +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        People people = (People) o;
        return Objects.equals(id, people.id) &&
                Objects.equals(name, people.name) &&
                Objects.equals(idCard, people.idCard);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, idCard);
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }
}

【8】Bean标签的scope属性

scope控制的是Bean的有效范围,有六种属性:
	- singleton:默认值。bean是单例的,每次获取Bean都是同一个对象
	- prototype:每次获取bean都重新实例化
	- request:每次请求重新实例化对象,同一个请求中多次获取时单例的
	- session:每个会话内bean是单例的
	- application:整个应用程序对象内bean是单例的
	- websocket:同一个websocket对象内对象是单例的

注意:
里面的singleton和prototype在Spring最基本的环境中就可以使用,不需要web环境。但是里面的request、session、application、websocket都只有在web环境才能使用。

Spring 中 Bean是否是线程安全的?
如果bean的scope是单例的,bean不是线程安全的
如果bean的scope是prototype,bean是线程安全的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
>
    <!--
         有效范围: scope
           singleton - 单例的,默认情况下,在ApplicationContext创建结束时,bean对象就都创建完成了。绝大多数情况下。使用单例即可。
             如:spring容器管理service服务对象,DAO数据访问对象等。
             可以使用bean标签的属性 lazy-init = "true",让ApplicationContext创建时,不初始化bean对象。
             lazy-init 默认是default,采用全局的,就是false。可选值有false和true。
             false,不延迟初始化,ApplicationContext创建时,就初始化bean对象。
             true。延迟初始化,getBean方法调用时,才初始化bean对象。
           prototype - 多例,在ApplicationContext创建结束时,不会创建这个scope有效范围的bean对象,直到getBean方法被调用,
             才创建bean对象,getBean方法调用多少次,创建多少对象。极少的情况下,需要用多例。
             如: 管理实体类型对象。 几乎没有什么项目会使用spring管理实体对象。
     -->

    <!-- bean对象的初始化方法。
         init-method="方法名"。 当bean对象创建,且属性注入后,调用的初始化方法
           一般用于资源初始化。 常用
         destroy-method = 方法名, 当bean对象被销毁前,执行的方法。一般用于回收资源。 不常用
         单例的bean对象。是在spring容器ApplicationContext销毁的时候,才会销毁
     -->

    <bean id="people" class="com.bjsxt.pojo.People" autowire="no" scope="singleton" lazy-init="false" init-method="init"
          destroy-method="init">
        <property name="id" value="10"/>
        <property name="name" value="张三"/>
        <!-- <property name="idCard" ref="idCard"/>-->
    </bean>

</beans>
    @Test
    public void testScope(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		System.out.println("spring容器创建完毕");
        People people = context.getBean("people", People.class);
        System.out.println(people);
    }

【9】Bean对象的初始化方法

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
>

    <!-- bean对象的初始化方法。
         init-method="方法名"。 当bean对象创建,且属性注入后,调用的初始化方法
           一般用于资源初始化。 常用
         destroy-method = 方法名, 当bean对象被销毁前,执行的方法。一般用于回收资源。 不常用
         单例的bean对象。是在spring容器ApplicationContext销毁的时候,才会销毁
     -->

    <bean id="people" class="com.bjsxt.pojo.People" init-method="init" destroy-method="init">
        <property name="id" value="10"/>
        <property name="name" value="张三"/>
        <!-- <property name="idCard" ref="idCard"/>-->
    </bean>

</beans>
package com.bjsxt.pojo;

import java.io.Serializable;
import java.util.Objects;

public class People implements Serializable {
    private Integer id;
    private String name;
    private IdCard idCard;

    public People() {
        System.out.println("无参数构造方法");
    }

    public People(IdCard idCard) {
        System.out.println("(IdCard idCard)参数构造方法");
        this.idCard = idCard;
    }

    public People(Integer id, String name, IdCard idCard) {
        System.out.println("(Integer id, String name, IdCard idCard) 参数构造方法");
        this.id = id;
        this.name = name;
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "People{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", idCard=" + idCard +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        People people = (People) o;
        return Objects.equals(id, people.id) &&
                Objects.equals(name, people.name) &&
                Objects.equals(idCard, people.idCard);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, idCard);
    }

    public void init(){
        System.out.println("初始化方法运行。");
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        System.out.println("setId方法运行");
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("setName方法运行");
        this.name = name;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        System.out.println("setIdCart方法运行:" + idCard);
        this.idCard = idCard;
    }
}

【10】单例模式

package com.bjsxt.singleton;

/*
    单例:希望类只有一个
    核心思想:
        1. 构造方法私有
        2. 对外提供一个能够获取对象的方法。

    饿汉式:
        优点:实现简单
        缺点:无论是否使用当前类对象,加载类时一定会实例化。
 */
public class Singleton {
    // 之所以叫做饿汉式:因为类加载时就创建了对象
    private static Singleton singleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return singleton;
    }
}

package com.bjsxt.singleton;

/**
 * 核心思想:
 * 1. 构造方法私有
 * 2. 对外提供一个能够获取对象的方法。
 *
 * 懒汉式优点和缺点:
 *  优点:
 *      按需创建对象。不会在加载类时直接实例化对象。
 *  缺点:
 *      写法相对复杂
 *      多线程环境下,第一次实例化对象效率低。
 */
class Singleton2 {
    //懒汉式:不会立即实例化
    private static Singleton2 singleton2;
    private Singleton2() {
    }
    public static Singleton2 getInstance() {
        if (singleton2 == null) {// 不是第一次访问的线程,直接通过if判断条件不成立。直接return
            synchronized (Singleton2.class) {
                if(singleton2==null) {// 防止多个线程已经执行到synchronized
                    singleton2 = new Singleton2();
                }
            }
        }
        return singleton2;
    }
}

【11】Spring 循环注入问题

在Spring IoC/DI使用过程中,可能出现循环注入的情况。当两个类都是用构造注入时,没有等当前类实例化完成就需要注入另一个类,而另一个类没有实例化完整还需要注入当前类,所以这种情况是无法解决循环注入问题的的。会出现BeanCurrentlyInCreationException异常。
如果两个类都使用设值注入且scope为singleton的就不会出现问题,可以正常执行。因为单例默认下有三级缓存(DefaultSingletonBeanRegistry),可以暂时缓存没有被实例化完成的Bean。但是如果两个类的scope都是prototype依然报BeanCurrentlyInCreationException
循环注入的多个bean,至少有一个是单例的,就可以实现循环注入。都是多例的,抛出异常
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
       default-autowire="constructor">

    <!-- 循环依赖|循环注入|循环设置 -->
    <bean id="a" class="com.bjsxt.pojo.A" scope="singleton">
        <property name="aName" value="aaa"></property>
        <property name="b" ref="b"></property>
        <!--<constructor-arg name="aName" value="aaa"></constructor-arg>
        <constructor-arg name="b" ref="b"></constructor-arg>-->
    </bean>

    <bean id="b" class="com.bjsxt.pojo.B" scope="singleton">
        <property name="bName" value="bbb"></property>
        <property name="a" ref="a"></property>
        <!--<constructor-arg name="bName" value="bbb"></constructor-arg>
        <constructor-arg name="a" ref="a"></constructor-arg>-->
    </bean>
</beans>
package com.bjsxt.pojo;

import java.io.Serializable;
import java.util.Objects;

public class A implements Serializable {
    private String aName;
    private B b;

    public A(String aName, B b) {
        this.aName = aName;
        this.b = b;
    }

    public A() {
        System.out.println("A无参构造运行");
    }

    @Override
    public String toString() {
        return "A{" +
                "aName='" + aName + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        A a = (A) o;
        return Objects.equals(aName, a.aName) &&
                Objects.equals(b, a.b);
    }

    @Override
    public int hashCode() {
        return Objects.hash(aName, b);
    }

    public String getaName() {
        return aName;
    }

    public void setaName(String aName) {
        this.aName = aName;
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        System.out.println("a.setB运行");
        this.b = b;
    }
}
package com.bjsxt.pojo;

import java.io.Serializable;
import java.util.Objects;

public class B implements Serializable {
    private String bName;
    private A a;

    public B(String bName, A a) {
        this.bName = bName;
        this.a = a;
    }

    public B() {
        System.out.println("B无参构造运行");
    }

    @Override
    public String toString() {
        return "B{" +
                "bName='" + bName + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        B b = (B) o;
        return Objects.equals(bName, b.bName) &&
                Objects.equals(a, b.a);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bName, a);
    }

    public String getbName() {
        return bName;
    }

    public void setbName(String bName) {
        this.bName = bName;
    }

    public A getA() {
        return a;
    }

    public void setA(A a) {
        System.out.println("b.setA运行");
        this.a = a;
    }
}
package com.bjsxt.test;

import com.bjsxt.pojo.A;
import com.bjsxt.pojo.B;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试循环注入
 *  不能使用构造注入,因为循环依赖,对方都未创建,无法作为参数,调用当前的有参构造方法。
 *  可以使用设置注入,因为可以先用无参构造创建对象,后调用setter做对象关系的设置注入。
 *   在Spring容器中,是使用三级缓存实现的。
 *   要求:循环注入的多个bean,至少有一个是单例的,就可以实现循环注入。都是多例的,抛出异常。
 */
public class TestCyc {
    @Test
    public void testCyc(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("ApplicationContext创建结束");
        A a = context.getBean("a", A.class);
        System.out.println(a);
        System.out.println(a.getB());

        System.out.println("==================================================");

        B b = context.getBean("b", B.class);
        System.out.println(b);
        System.out.println(b.getA());
    }
}

【12】BeanFactory和ApplicationContext

BeanFactory是Spring中的顶级接口,接口中定了Spring容器最基本功能。是Spring IoC的最核心接口。
BeanFactory最常用实现类是XmlBeanFactory
从Spring 3.1 版本开始,使用DefaultListableBeanFactory和XMLBeanDefinitionReader替代了XmlBeanFactory
无论是使用哪个写法,都可以发现BeanFactory是在真正getBean的时候才去实例化的

ApplicationContext是BeanFactory的子接口。所以要比BeanFactory的功能更加强大,除了BeanFactory的功能,还包含了:
	- AOP 功能
	- 国际化(MessageSource)
	- 访问资源,如URL和文件(ResourceLoader)
	- 消息发送机制(ApplicationEventPublisher)
	- Spring集成Web时的WebApplicationContext
	
在使用时ApplicationContext时多使用ClassPathXmlApplicationContext
ApplicationContext是在加载文件后立即创建Bean。可以通过lazy-init属性进行控制,让bean懒加载
package com.bjsxt.test;

import com.bjsxt.pojo.A;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

/**
 * 测试BeanFactory
 */
public class TestBeanFactory {
    @Test
    public void testBeanFactory2(){
        BeanFactory factory = new DefaultListableBeanFactory();
        // 基于XML配置文件的bean定义读取解析类型。
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(
                ((DefaultListableBeanFactory)factory)
        );
        // 读取XML配置文件,并让BeanFactory解析配置中的所有内容。
        reader.loadBeanDefinitions(
                new FileSystemResource("C:\\ideaWorkspace\\sj125_spring\\sj125_spring_2\\src\\main\\resources\\applicationContext.xml")
        );
        System.out.println("===================================");
        A a = factory.getBean("a", A.class);
        System.out.println(a);
    }

    @Test
    public void testBeanFactory1(){
        FileSystemResource resource =
                new FileSystemResource("C:\\ideaWorkspace\\sj125_spring\\sj125_spring_2\\src\\main\\resources\\applicationContext.xml");
        // 已过时。不推荐使用
        BeanFactory beanFactory = new XmlBeanFactory(resource);
        System.out.println("BeanFactory创建结束");
        A a = beanFactory.getBean("a", A.class);
        System.out.println(a);
        
        ClassPathXmlApplicationContext applicationContext =
        new ClassPathXmlApplicationContext("applicationContext.xml");
        Object teacher = applicationContext.getBean("teacher");
    }
}

【13】Spring整合Web

Spring框架整合Web应用需要的配置如下:
  1. web.xml必要配置
     配置一个Listener,去读取Spring的配置文件,并创建ApplicationContext。
     把创建好的容器ApplicationContext保存到ServletContext作用域(application)中。
     
     这个Listener是Spring框架提供的,在spring-web.jar包中。org.springframework.web.context.ContextLoaderListener
     在这个Listener中,会主动的读取web.xml配置文件中的上下文初始化参数。<context-param>。参数的名字是 contextConfigLocation,
     参数的值就是spring配置文件的位置。如果spring配置文件在类路径下,可以增加前缀 classpath:
     监听器,启动后,读取初始化参数contextConfigLocation,并创建ApplicationContext接口的子接口类型WebApplicationContext的容器对象。
     创建后的容器,保存在ServletContext变量作用域中,attributeName是WebApplicationContext类的全命名.ROOT  。
  2. spring配置(暂时是必要的)
     使用bean标签,配置需要让spring容器管理的那些对象。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 配置listener可读取解析的上下文初始化参数 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- 配置监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
       default-autowire="default" >

    <!-- 暂时只需要配置service -->
    <bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl"></bean>

</beans>
package com.bjsxt.controller;

import com.bjsxt.pojo.User;
import com.bjsxt.service.UserService;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private UserService userService;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        userService =
                WebApplicationContextUtils.findWebApplicationContext(config.getServletContext())
                        .getBean("userService", UserService.class);
    }

    /**
     * 登录
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        User user = userService.login(username, password);
        if (user == null) {
            // 登录失败
            resp.sendRedirect("/fail.jsp");
        }
        // 登录成功
        resp.sendRedirect("/success.jsp");
    }
}

【14】Spring整合Mybatis

spring整合MyBatis
  所谓的整合MyBatis,就是让Spring容器去创建SqlSessionFactory,并且为Mapper接口做接口绑定。
  一旦整合成功。Spring容器中存在SqlSessionFactory,可以基于工厂创建SqlSession,可以基于会话getMapper获取接口绑定对象。
  Spring整合MyBatis后,自动提交事务。这是spring框架修改了mybatis的默认事务管理机制。实际上是mybatis-spring.jar修改了mybatis框架的
  默认事务管理机制。

  1. 写spring配置文件。必要
  2. 也可以写mybatis.cfg.xml配置文件,可选。 一般只有设置plugin的时候,写这个配置文件。
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjsxt</groupId>
    <artifactId>springmybatis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.11.6</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.16</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.16</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.16</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <!-- Tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/bjsxt</path><!--发布到Tomcat的名称,即URL访问名称,平时多使用/-->
                    <port>8081</port><!-- 端口,平时多使用80或8080 -->
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
<!-- web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!-- 配置Spring容器的配置文件名 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!--配置监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
       default-autowire="default">

    <!-- Spring整合MyBatis时,需要定义若干bean对象。
         分别是:
          1. dataSource
          2. SqlSessionFactory
          3. MapperScannerConfigurer
      -->
    <!-- 定义数据库连接池, 数据源
         DriverManagerDataSource - spring框架提供的DataSource接口实现类。
     -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 基于mybatis-spring.jar提供的SqlSessionFactoryBean类型,创建会话工厂SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <!-- 读取mybatis.cfg.xml配置文件 -->
        <property name="configLocation" value="classpath:mybatis.cfg.xml"></property>
        <!--<property name="typeAliasesPackage" value="com.bjsxt.pojo"></property>-->
        <!--<property name="mapperLocations" value="com/bjsxt/mapper/*.xml"></property>-->
    </bean>

    <!-- 配置MapperScannerConfigurer, 扫描接口和配置文件,创建接口绑定对象 -->
    <bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 和SqlSessionFactory产生联系 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
         <!-- 
			扫描的包: 
			MapperScannerConfigurer 的 basePackage 属性指定了需要扫描的包路径 com.bjsxt.mapper,
			这意味着 Spring 在启动时会扫描这个包及其子包下的所有接口。
			扫描到的每个接口会被 MyBatis-Spring 自动注册为一个 Mapper 接口,Spring 将会为这些接口生成代理类(实现类)。
			这些代理类(实现类)会被 Spring 实例化并管理,成为 Spring 容器中的一个 bean
		-->
        <property name="basePackage" value="com.bjsxt.mapper"></property>
    </bean>

    <!-- 配置要管理的Service对象 -->
    <bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl">
        <property name="userMapper" ref="userMapper"></property>
    </bean>
</beans>
  • 在resources文件夹的目录下直接新建mybatis.cfg.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="com.bjsxt.pojo"/>
    </typeAliases>
</configuration>
package com.bjsxt.pojo;

public class User {
    private int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
package com.bjsxt.mapper;

import com.bjsxt.pojo.User;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {
    @Select("select * from user where username=#{username} and password=#{password}")
    User selectByUser(User user);
}

package com.bjsxt.service.impl;

import com.bjsxt.mapper.UserMapper;
import com.bjsxt.pojo.User;
import com.bjsxt.service.UserService;

public class UserServiceImpl implements UserService {
    private UserMapper userMapper;

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public User login(User user) {
        return userMapper.selectByUser(user);
    }
}

package com.bjsxt.servlet;

import com.bjsxt.pojo.User;
import com.bjsxt.service.UserService;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    UserService userService;
    @Override
    public void init(ServletConfig config) throws ServletException {
        WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(config.getServletContext());
        userService = wac.getBean("userService", UserService.class);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String username = req.getParameter("username");
//        username = new String(username.getBytes("iso-8859-1"),"utf-8");
        String password = req.getParameter("password");
        User user = new User(0, username, password);
        User result = userService.login(user);
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        if(result!=null){
            out.print("登录成功");
        }else{
            out.print("登录失败");
        }
    }
}
  • 创建测试类
package com.bjsxt;

import com.bjsxt.pojo.User;
import com.bjsxt.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpringMyBatis {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 获取UserService对象
        UserService userService = context.getBean("userService", UserService.class);

        System.out.println("=============================================================");
        // 获取容器中所有的bean对象名字。迭代打印查看
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            Object bean = context.getBean(beanName);
            System.out.println(beanName + " - " + bean.getClass().getName() + " - " + bean);
        }
        System.out.println("=============================================================");
    }
}

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="login" mehtod="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>

【15】Maven 占位符/ EL 表达式 / Spring 占位符

Maven 占位符主要用于在 Maven 构建过程中替换配置文件中的占位符为实际的值。如: 在 pom.xml 中配置资源过滤,并在资源文件中使用占位符
EL 表达式主要用于 Java EE 技术中,用来访问和操作 JavaBean 的属性,尤其在 JSP 页面和 JSF 表达式语言中常见。
Spring 占位符用于在 Spring 应用程序中引用外部化的配置值,可以用于 XML 配置文件或者通过 @Value 注解注入到 Spring Bean 中
  • Maven 占位符
<!-- pom.xml -->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
# config.properties
database.url=${db.url}
database.username=${db.username}
  • Spring 占位符
# database.properties 
database.url=jdbc:mysql://localhost:3306/mydb
database.username=admin
database.password=admin123
<!-- applicationContext.xml -->
<context:property-placeholder location="classpath:database.properties"/>

<!-- 定义数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="url" value="${database.url}"/>
    <property name="username" value="${database.username}"/>
    <property name="password" value="${database.password}"/>
</bean>
mport org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MyComponent {
    
    @Value("${database.url}")
    private String dbUrl;

    @Value("${database.username}")
    private String dbUsername;

    // getters and setters
}

【16】Spring常用注解

注解名称解释
@Component实例化Bean,默认名称为类名首字母变小写。支持自定义名称
@Repository@Component子标签。作用和@Component一样。用在持久层
@Service@Component子标签。作用和@Component一样。用在业务层
@Controller@Component子标签。作用和@Component一样。用在控制器层
@Configuration@Component子标签。作用和@Component一样。用在配置类
@Autowired自动注入。默认byType,如果多个同类型bean,使用byName(默认通过属性名查找是否有同名的bean,也可以通过@Qualifier(“bean名称”),执行需要注入的Bean名称)
@Resource非Spring注解。默认byName,如果没找到,使用byType。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd"
       default-autowire="default">

    <!-- 使用注解的要求:
         1. 类型上、属性上、方法上、参数前,定义使用Spring的注解。
         2. 在配置文件中,增加新的namespace。
            配置方式。先增加namespace,再增加schemaLocation
         3. 配置标签,通知spring框架,创建容器的时候,要去哪些包中扫描注解,并让注解生效。
     -->
    <!-- 组件扫描标签。让spring容器去哪些包中扫描注解
         属性base-package: 具体要扫描的包。
         赋值方式:
          1. 单独的包,直接编写包名即可。
          2. 多个包,定义若干包,包之间用逗号分隔。必须是英文逗号。
          3. 统配定义,要扫描包名前缀是com.bjsxt的所有子包。定义为 com.bjsxt.*
          4. 包含当前包的统配定义,要扫描的包是com.bjsxt及其所有的子孙包,定义为 com.bjsxt
         配置的时候,不要贪图简单,尽量精确配置。 不推荐的配置方式: com   com.*   org.*   org
     -->
    <context:component-scan
            base-package="com.bjsxt.dao.impl,com.bjsxt.dao,com.bjsxt.*,com.bjsxt"></context:component-scan>

</beans>

【17】@Configuration 和 @Bean

package com.bjsxt.config;

import com.bjsxt.dao.UserDao;
import com.bjsxt.dao.impl.UserDaoImpl;
import com.bjsxt.service.UserService;
import com.bjsxt.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 当前是一个配置类型,功能类似于配置文件applicationContext.xml
 *
 * @Configuration - 当前类型被spring容器管理,创建bean对象。 当前类型的对象,是配置对象。
 * 一般用于创建其他需要使用的bean对象。
 * @Bean - 定义在方法上。代表这个方法的返回值,是一个需要spring容器管理的bean对象
 * 相当于用方法定义了一个<bean>标签。bean对象的类型,就是方法返回值类型。bean对象的唯一标记,默认是方法名字。
 * 属性value,为bean对象定义唯一标记。默认唯一标记失效。
 */
@Configuration
public class MyConfiguration {
    @Bean
    public UserDao userDao() {
        System.out.println("使用Configuration创建UserDao对象");
        return new UserDaoImpl();
    }

//    @Autowired
//    @Qualifier("userDao")
//    private UserDao userDao;

    /**
     * 在Configuration的Bean定义方法中,希望获取spring容器中的其他对象的方式有:
     * 1. 在当前类型中定义属性,并使用注解@Autowired赋值。
     * 如果,spring容器中有多个同类型的bean对象,且没有同名的bean对象,需要指定名称时,使用注解实现
     * 2. 在当前的方法参数表,定义需要使用的对象,自动从spring容器中先byType再byName获取对象。
     * 如果,spring容器中有多个同类型的bean对象,且没有同名的bean对象,需要指定名称时,使用注解实现
     * 3. 直接调用当前类型中的创建bean对象的方法.spring容器在编译执行期,可以动态识别,
     * 调用的方法,是否是@Bean注解修饰的方法,如果是,则从容器中获取对应的bean对象,直接返回。
     *
     * @return
     */
    @Bean("userService")
    public UserService suibian(/*@Qualifier("userDao") UserDao userDao*/) {
        System.out.println("使用Configuration创建UserService对象");
        UserServiceImpl userService = new UserServiceImpl();
        // 为属性赋值
        // userService.setUserDao(userDao);
        userService.setUserDao(userDao());

        return userService;
    }
}

【18】@Autowired

@Service
public class UserServiceImpl implements UserService {
    
    // set方法注入简写
    @Autowired
    private UserRepository userRepository;

    // 构造函数注入
    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 方法注入
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public void createUser(String username) {
        System.out.println("Creating user: " + username);
    }
}

【19】@ContextConfigration

package com.bjsxt.test;

import com.bjsxt.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 测试SpringTest
 * 在测试类型上,增加2个新的注解使用。
 * 1. RunWith - 定义具体辅助执行测试的运行器。用哪一个运行器,去创建容器对象,实现注解注入等功能。
 * SpringJUnit4ClassRunner - 是Spring Test为Junit提供的运行器
 * 2. ContextConfiguration - 配置spring配置文件具体地址的。通知运行器去读取什么配置文件
 * value属性,配置String[]类型的值,代表若干配置文件,如果只有一个,可以省略{}
 * 赋值的时候,一定要写classpath:前缀。
 * 当前类型中的任意测试方法(@Test注解修饰的方法),执行的时候,都可以让spring-test提供辅助支持。
 * 当前类型中的任意属性,都可以使用注解@Autowired从容器中获取bean对象,并注入。
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestSpringTest {
    @Autowired
    private UserService userService;

    @Test
    public void testUserService() {
        userService.add();
    }
}

【20】AOP

Aspect:切面
join point: 切入点。也称目标方法。即对哪个方法做扩展、做增强
Pointcut:切点。即表达式,通过表达式说明哪些方法做扩展、做增强(join point 的批量处理)
Advice:通知,增强内容
AOP Proxy:代理。Spring支持JDK动态代理和cglib动态代理两种方式,可以通过proxy-target-class=true把默认的JDK动态代理修改为Cglib动态代理

【21】Schema-based

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.16</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd"
       default-autowire="default">

    <!-- 增加aop namespace -->
    
    <!-- 配置目标bean对象 -->
    <bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl"></bean>

    <!-- 前置通知对象 -->
    <bean id="beforeAdvice" class="com.bjsxt.advice.MyBeforeAdvice"></bean>
    <!-- 后置通知对象 -->
    <bean id="afterReturningAdvice" class="com.bjsxt.advice.MyAfterReturningAdvice"></bean>
    <!-- 环绕通知 -->
    <bean id="aroundAdvice" class="com.bjsxt.advice.MyAroundAdvice"></bean>
    <!-- 异常通知 -->
    <bean id="throwsAdvice" class="com.bjsxt.advice.MyThrowsAdvice"></bean>
    <bean id="beforeAndAfter" class="com.bjsxt.advice.MyBeforeAndAfterAdvice"></bean>

    <!-- 基于schema-based,配置aop
         aop:config - 父标签,代表要开始定义aop内容
         aop:point-cut - 子标签,定义PointCut表达式
           id - 唯一标记
           expression - 表达式
         aop:advisor - 织入wearing后,生成的那个代理对象 AOP Proxy。是在定义织入动作,结果是生成代理对象。
              这个代理对象的bean名称就是被代理的目标bean对象的名称。
           advice-ref - 要织入的advice bean对象唯一标记
           pointcut - 直接使用表达式,定义PointCut
           pointcut-ref - 引用已定义的PointCut

         多个advisor,执行顺序是,由上至下的配置顺序。按照栈模式执行(栈遵循先进后出(LIFO)的原则)。
     -->
    <aop:config>
        <!-- 表达式语法:
             要找到运行的那个目标方法。运行 - execution( point-cut表达式 )
             表达式 :  返回值 包.子包....类型名称.方法名称(参数表每个参数的类型 多个参数逗号分隔)
             表达式通配符 : * 代表一切。   ..  代表任意类型,任意个参数
         -->
        <aop:pointcut id="firstPointCut"
                      expression="execution( void com.bjsxt.service.impl.UserServiceImpl.sayHello() )"/>
        <aop:pointcut id="secondPointCut"
                      expression="execution( java.lang.String com.bjsxt.service.UserService.showInfo() )"/>

        <aop:pointcut id="allPointCut" expression="execution( * com.bjsxt.service.*.*(..) )"/>

        <aop:advisor advice-ref="beforeAdvice" pointcut-ref="firstPointCut"></aop:advisor>
        <aop:advisor advice-ref="beforeAdvice" pointcut-ref="secondPointCut"></aop:advisor>
        <aop:advisor advice-ref="beforeAdvice"
                     pointcut="execution( java.lang.String com.bjsxt.service.UserService.sayHello( java.lang.String ) )"></aop:advisor>

        <aop:advisor advice-ref="afterReturningAdvice" pointcut-ref="allPointCut"></aop:advisor>
      
        <aop:advisor advice-ref="aroundAdvice" pointcut-ref="allPointCut"></aop:advisor>

        <aop:advisor advice-ref="throwsAdvice" pointcut-ref="allPointCut"></aop:advisor>
        <aop:advisor advice-ref="beforeAndAfter" pointcut-ref="allPointCut"></aop:advisor>


    </aop:config>

</beans>
package com.bjsxt.advice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 前置通知。在方法运行前,增加的功能。
 */
public class MyBeforeAdvice implements MethodBeforeAdvice {
    /**
     * 目标方法运行前,要做的工作。方法的参数,一般是让程序员做判断校验使用的,不是让程序员修改的。
     *
     * @param method 要运行的目标方法对象。
     * @param args   要运行的目标方法参数表。可以修改,不建议修改。
     * @param target 要运行的目标方法,对应的对象。
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("方法前置通知,运行的方法名是:" + method.getName() + ", 运行方法的参数是:" + Arrays.toString(args) + ", 这个方法依托的对象是:" + target);
        /*if(args.length > 0){
            args[0] = "李四";
        }*/
    }
}

package com.bjsxt.advice;

import com.bjsxt.pojo.User;
import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 后置通知
 */
public class MyAfterReturningAdvice implements AfterReturningAdvice {
    /**
     * 只有方法正常执行结束,才运行的。当前通知执行时,会中断返回值的返回,等当前通知执行结束后,再返回。
     *
     * @param returnValue 目标方法返回值,可以修改,不建议修改。
     * @param method      目标方法
     * @param args        目标方法执行时的参数表
     * @param target      目标方法执行时,依托的目标对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置通知!返回值:" + returnValue + ", 方法名:" + method.getName() + ", 参数表:" + Arrays.toString(args) + ", 目标对象:" + target);
        /*if(args.length > 0){
            args[0] = "王五";
        }*/
        // 尝试修改方法返回值和方法参数中的字符串内容都无法生效,
        // 因为字符串的不可变性导致这些操作只是在内存中创建了新的字符串对象,并没有改变原始的字符串对象
        // returnValue = "后置拦截器,修改方法返回值"; // 字符串对象特性,导致修改后,外部也看不到修改后的结果。
        /*if(returnValue instanceof User){
            ((User) returnValue).setId(100);
        }*/
    }
}
package com.bjsxt.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 环绕通知
 */
public class MyAroundAdvice implements MethodInterceptor {
    /**
     * 环绕通知方法,要求必须调用invocation.proceed()方法,并把invoke方法的返回值返回。
     * <p>
     * 所谓环绕,就是手工调用目标方法。在调用前、调用后、抛出异常时,分别执行某逻辑。
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        Object returnValue = null;

        try {
            // 前置通知
            System.out.println("环绕通知 - 前置");

            // 调用目标方法
            returnValue = invocation.proceed();

            // 后置通知
            System.out.println("环绕通知 - 后置");
        } catch (Exception e) {
            // 异常处理通知
            e.printStackTrace();
            System.out.println("环绕通知 - 异常");
            // 如果异常,需要传递,也就是传递给调用者,直接throw e 即可
            throw e;
        }
        // 返回目标方法返回值
        return returnValue;
    }
}
package com.bjsxt.advice;

import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

/**
 * 异常通知
 * ThrowsAdvice - 标记接口,没有定义任何方法。只标记当前类型是异常通知。
 * 当前类型中,可以定义若干通知方法,只能是重载方法。如果目标方法抛出异常,则就近匹配异常类型,并运行通知方法。
 * 如果定义了重载的同异常类型,不同参数个数的方法,抛出异常是,就近匹配异常类型。
 * 可定义的方法有若干,方法签名规则只有两种,分别是: 签名规则记不住,看源码的注释。
 * 1. 一个参数:  public void afterThrowing(T<T extends Exception> ex)
 * 参数的含义是:当目标方法抛出这个参数异常类型或其子类型时,当前通知方法运行。
 * 2. 四个参数: public void afterThrowing(Method method, Object[] args, Object target, T<T extends Exception> ex)
 * 参数的含义:
 * method : 目标方法对象
 * args : 目标方法运行参数
 * target : 目标方法运行依托的对象
 * ex: 当目标方法抛出这个类型或子类型异常时,当前通知方法运行。
 */
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, NullPointerException ex) {
        System.out.println("异常通知 - 4个参数 - 空指针异常");
    }

    public void afterThrowing(NullPointerException ex) {
        System.out.println("异常通知 - 空指针异常");
    }

    public void afterThrowing(RuntimeException ex) {
        System.out.println("异常通知 - 运行时异常");
    }

    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        System.out.println("异常通知 - 4个参数 - 异常");
    }

    public void afterThrowing(Exception ex) {
        System.out.println("异常通知 - 异常");
    }
}
package com.bjsxt.service.impl;

import com.bjsxt.pojo.User;
import com.bjsxt.service.UserService;

import java.io.IOException;

public class UserServiceImpl implements UserService {
    @Override
    public User login(String username, String password) throws Exception {
        System.out.println("登录方法运行: name = " + username + " , password = " + password);
        if (username == null) {
            throw new RuntimeException();
        }
        return new User();
    }

    @Override
    public void sayHello() {
        System.out.println("你好,不要睡觉,不要玩手机!!!   UserServiceImpl.sayHello() 方法运行!!!");
    }

    @Override
    public String showInfo() {
        System.out.println("UserServiceImpl.showInfo()方法运行!!  大家好。我是堂堂,未婚,没车,没房,赚的多,花的少,。。。得早!!!");
        return "大家好。我是堂堂,未婚,没车,没房,赚的多,花的少,。。。得早!!!";
    }

    @Override
    public String sayHello(String name) {
        System.out.println("UserServiceImpl.sayHello(String)方法运行!! 你好," + name + "。UserServiceImpl.sayHello(String name) 方法运行 !!!");
        return "你好," + name + "。UserServiceImpl.sayHello(String name) 方法运行 !!!";
    }

    @Override
    public User getUserById(Integer id) throws Exception {

        User user = new User();
        user.setId(id);
        user.setName("姓名 - " + id);
        System.out.println("UserServiceImpl.getUserById(Integer)方法运行,返回:" + user);

        if (id == 10) {
            throw new NullPointerException();
        } else if (id == 11) {
            throw new RuntimeException();
        } else if (id == 12) {
            throw new Exception();
        } else if (id == 13) {
            throw new IndexOutOfBoundsException();
        } else if (id == 14) {
            throw new IOException();
        }

        return user;
    }
}
package com.bjsxt.test;

import com.bjsxt.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAOP {
    @Autowired
    private UserService userService;
    
    @Test
    public void testAdvice() throws Exception {
        System.out.println(userService.getClass().getName());
        userService.sayHello();
        System.out.println("============================================");
        System.out.println("打印返回结果:" + userService.showInfo());
        System.out.println("============================================");
        System.out.println("打印返回结果:" + userService.sayHello("张三"));
        System.out.println("============================================");
        System.out.println("打印返回结果:" + userService.getUserById(1));
        System.out.println("==================空指针异常==========================");
        System.out.println("打印返回结果:" + userService.getUserById(10));
        System.out.println("==================运行时异常==========================");
        // System.out.println("打印返回结果:" + userService.getUserById(11));
        System.out.println("==================异常==========================");
        // System.out.println("打印返回结果:" + userService.getUserById(12));
        System.out.println("==================下标越界异常==========================");
        // System.out.println("打印返回结果:" + userService.getUserById(13));
        System.out.println("==================IO异常==========================");
        // System.out.println("打印返回结果:" + userService.getUserById(14));
    }

}

【22】AspectJ

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd"
       default-autowire="default">

    <!-- 增加aop namespace -->
    
    <!-- 配置目标bean对象 -->
    <bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl"></bean>
    
    <!-- 配置自定义的Aspect 通知bean对象 -->
    <bean id="firstAspect" class="com.bjsxt.aspect.FirstAspectAdvice"></bean>
    <bean id="secondAspect" class="com.bjsxt.aspect.SecondAspectAdvice"></bean>

    <aop:config>
        <aop:aspect ref="secondAspect">
            <!-- 表达式语法:
                 execution(返回值 包.接口或类型.方法名(按照顺序给参数定义类型)) and args(参数变量名1, 参数变量名2...)
                 表达式中的参数名,和真实方法的参数名无关。
             -->
            <aop:pointcut id="loginPointCut"
                          expression="execution(com.bjsxt.pojo.User com.bjsxt.service.UserService.login(java.lang.String, java.lang.String) ) and args(username, password) "/>
            <!-- arg-names = "表达式中的参数名称"  绑定参数做传递的时候,参数名称,必须是表达式中定义的参数名称。 建议见名知义 -->
            <aop:before method="before" pointcut-ref="loginPointCut" arg-names="username, password"></aop:before>
            <aop:after-returning method="after" pointcut-ref="loginPointCut" arg-names="returnValue, username, password"
                                 returning="returnValue"></aop:after-returning>
            <aop:around method="around" pointcut-ref="loginPointCut" arg-names="point, username, password"></aop:around>
            <aop:after-throwing method="throwing" pointcut-ref="loginPointCut" arg-names="ex, username, password"
                                throwing="ex"></aop:after-throwing>
        </aop:aspect>

        <!-- aspect的配置,需要先配置父标签 aspect, 再配置子标签 point-cut 和 advisor(通过标签配置具体的通知,如前置,后置等)
             ref - 引用自定义的Aspect通知对象
         -->
        <aop:aspect ref="firstAspect">
            <aop:pointcut id="all" expression="execution( * com.bjsxt.service.*.* (..) )"/>
            <!-- 前置通知。 method 方法名,   -->
            <aop:before method="before" pointcut-ref="all"/>
            <!-- 后置通知。正常结束后的通知。 returning,方法参数名对应目标方法返回值 -->
            <aop:after-returning method="afterReturning" pointcut-ref="all" returning="returnValue"/>
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="all"/>
            <!-- 异常通知 throwing 方法参数名对应目标方法抛出异常 -->
            <aop:after-throwing method="throwing" pointcut-ref="all" throwing="ex"/>
        </aop:aspect>

    </aop:config>

</beans>
package com.bjsxt.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 采用Aspect实现AOP的通知。
 * 不需要实现任何接口。
 * 定义若干方法。通过配置,指定方法的具体通知类型。
 * 方法定义时,有签名的要求:
 * 前置   public void xxxx(..) [throws Throwable]
 * 后置   public void xxxx([Object returnValue]..) [throws Throwable] 目标方法的返回值可以通过参数获取,但是必须有特定的配置
 * 环绕   public Object xxxx(ProceedingJoinPoint , ..)[throws Throwable] ProceedingJoinPoint代表连接点,通过这个参数执行目标方法
 * 异常   public void xxxx([T<T extends Exception>]..)[throws Throwable] 异常参数,用于就近匹配目标方法抛出的异常
 */
public class FirstAspectAdvice {
    // 前置
    public void before() throws Throwable {
        System.out.println("Aspect 前置通知");
    }

    // 后置
    public void afterReturning(Object returnValue) throws Throwable {
        System.out.println("Aspect 后置通知, 返回值是:" + returnValue);
    }

    // 环绕
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object returnValue = null;
        try {
            System.out.println("Aspect 环绕通知, 前置");
            returnValue = point.proceed();
            System.out.println("Aspect 环绕通知, 后置");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Aspect 环绕通知, 异常");
            throw e;
        }
        return returnValue;
    }

    // 异常 Exception
    public void throwing(Exception ex) throws Throwable {
        System.out.println("Aspect 异常通知");
    }
}
package com.bjsxt.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 处理目标方法参数
 */
public class SecondAspectAdvice {
    // 前置
    public void before(String username, String password) throws Throwable {
        System.out.println("前置: username = " + username + " , password = " + password);
    }

    // 后置
    public void after(Object returnValue, String username, String password) {
        System.out.println("后置: username = " + username + " , password = " + password + ", 返回 = " + returnValue);
    }

    // 环绕
    public Object around(ProceedingJoinPoint point, String username, String password) throws Throwable {
        System.out.println("环绕 - 前置: username = " + username + " , password = " + password);
        Object returnValue = point.proceed(new Object[]{username, password});
        System.out.println("环绕 - 后置: username = " + username + " , password = " + password);
        return returnValue;
    }

    // 异常
    public void throwing(Exception ex, String username, String password) {
        System.out.println("异常: username = " + username + " , password = " + password);
    }
}

【23】String的不可变性

public class ImmutableStringInMethod {
// 字符串对象一旦创建就不可更改的特性。即使在方法中对字符串进行了操作,实际上也是创建了一个新的字符串对象,而原始字符串对象始终保持不变
    public static void main(String[] args) {
        String originalStr = "Hello";
        System.out.println("原始字符串: " + originalStr); // 输出: Hello

        String modifiedStr = modifyString(originalStr);
        System.out.println("修改后的字符串: " + modifiedStr); // 输出: Hello, World!

        System.out.println("原始字符串未改变: " + originalStr); // 输出: Hello,仍然是原始的字符串
    }

    public static String modifyString(String str) {
        // 在方法中修改字符串内容
        str = str + ", World!";
        return str;
    }
}

【24】Schema-based和AspectJ区别

Schema-based 基于接口实现, AspectJ 基于配置实现的
Schame-based是运行时增强,AspectJ是编译时增强
切面比较多时,最好选择AspectJ方式

【25】注解方式实现AOP

package com.bjsxt.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 当前类型是一个通知。使用注解做描述。
 * 是基于AspectJ实现的
 *
 * @Aspect - 只是说明当前类型是一个通知。
 * 注意: 一定要使用Component注解修饰当前类型。让spring容器创建当前类型的bean对象。
 * <p>
 * 基于注解的AOP实现,是运行时,生成AOP Proxy  AOP 代理对象。
 * getBean方法调用时,动态生成代理对象。
 * 在性能上,相对比配置的AspectJ慢一点。 通知越多,性能越低。 通知少,性能没有太大的区别。
 */
@Aspect
@Component
public class MyAdvice {
    /**
     * 定义各种通知方法。
     */
    /**
     * 前置通知
     *
     * @Before - 前置通知注解
     * 属性
     * value - pointcut表达式
     * argNames - 要传入的参数。需要和pointcut表达式中定义的参数名称一致。同时和方法的参数名称一致
     */
    @Before("execution( void com.bjsxt.service.impl.UserServiceImpl.showInfo() )")
    public void showInfoBefore() throws Throwable {
        System.out.println("showInfo方法的前置通知");
    }

    @Before(value = "execution( java.lang.String com.bjsxt.service.impl.UserServiceImpl.sayHello(java.lang.String) ) && args(name)", argNames = "name")
    public void sayHelloBefore(String name) throws Throwable {
        System.out.println("sayHello方法前置通知, name = " + name);
    }

    /**
     * 后置通知
     *
     * @After - 后置通知,方法是否正常结束(包括抛出异常)都运行的。
     * @AfterReturning - 后置通知,方法正常结束才运行的。
     * value - pointcut表达式
     * returning - 目标方法的返回值,通过当前方法的哪一个参数传入。
     * argNames -
     */
    @AfterReturning(value = "execution( void com.bjsxt.service.impl.UserServiceImpl.showInfo() )", returning = "returnValue")
    public void showInfoAfterReturning(Object returnValue) {
        System.out.println("showInfo方法的后置通知, 返回值 = " + returnValue);
    }

    @AfterReturning(value = "execution( java.lang.String com.bjsxt.service.impl.UserServiceImpl.sayHello(java.lang.String) ) && args(name)", returning = "returnValue", argNames = "returnValue, name")
    public void sayHelloAfterReturning(Object returnValue, String name) {
        System.out.println("sayHello方法的后置通知, name = " + name + " ; 返回值 = " + returnValue);
    }

    /**
     * 环绕通知
     *
     * @Around - 环绕通知
     * value - pointcut表达式
     * argNames
     */
    @Around("execution( void com.bjsxt.service.impl.UserServiceImpl.showInfo() )")
    public Object showInfoAround(ProceedingJoinPoint point) throws Throwable {
        try {
            System.out.println("showInfo方法环绕通知 - 前置");
            Object returnValue = point.proceed();
            System.out.println("showInfo方法环绕通知 - 后置");
            // 返回目标方法的返回值。
            return returnValue;
        } catch (Exception e) {
            System.out.println("showInfo方法环绕通知 - 异常");
            throw e;
        }
    }

    @Around(value = "execution( java.lang.String com.bjsxt.service.impl.UserServiceImpl.sayHello(java.lang.String) ) && args(name)", argNames = "point, name")
    public Object sayHelloAround(ProceedingJoinPoint point, String name) throws Throwable {
        try {
            System.out.println("sayHello环绕 - 前置");
            Object returnValue = point.proceed(new Object[]{name});
            System.out.println("sayHello环绕 - 后置");
            return returnValue;
        } catch (Exception e) {
            System.out.println("sayHello方法环绕 - 异常");
            throw e;
        }
    }

    /**
     * 异常通知
     *
     * @AfterThrowing - 异常通知
     * value - pointcut表达式
     * argNames
     */
    @AfterThrowing(value = "execution( void com.bjsxt.service.impl.UserServiceImpl.showInfo() )", throwing = "e")
    public void showInfoAfterThrowing(Exception e) {
        System.out.println("showInfo方法的异常通知,异常类型是:" + e.getClass().getName());
    }

    @AfterThrowing(value = "execution( java.lang.String com.bjsxt.service.impl.UserServiceImpl.sayHello(java.lang.String) ) && args(name)", throwing = "e", argNames = "e, name")
    public void sayHelloAfterThrowing(Exception e, String name) {

    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd"
       default-autowire="default">

    <!-- 扫描注解 -->
    <context:component-scan base-package="com.bjsxt"></context:component-scan>

    <!-- 开启aop注解支持
         使用CGLIB生成代理对象。
         <aop:aspectj-autoproxy  proxy-target-class="true" />
         使用JDK生成代理对象
         <aop:aspectj-autoproxy expose-proxy="true"/>
     -->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

    <!-- 手工配置AOP支持。 两个标签有同名属性。特别容易出现错误。
         JDK生成代理对象
         <aop:config expose-proxy="true" />
         CGLIB生成代理对象
         <aop:config proxy-target-class="true" />
     -->
    <!--<aop:config expose-proxy="true" proxy-target-class="true" />-->

</beans>
package com.bjsxt.service.impl;

import com.bjsxt.service.UserService;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

/**
 * 目标类型。真实的类型
 */
@Service
public class UserServiceImpl implements UserService {

    /**
     * 注解
     *
     * @Pointcut - 切点,通过表达式,描述当前方法的被切入的点。
     */
    @Pointcut("execution( void com.bjsxt.service.impl.UserServiceImpl.showInfo() )")
    @Override
    public void showInfo() {
        System.out.println("程序猿已经被规划为民工行列!!!");
    }

    /**
     * 注解
     *
     * @Pointcut - 切点。被切入的点。
     * 表达式。和配置文件中的写法一致。只有 and -> &&。
     */
    @Override
    @Pointcut("execution( java.lang.String com.bjsxt.service.impl.UserServiceImpl.sayHello(java.lang.String) ) && args(name) ")
    public String sayHello(String name) {
        System.out.println("您好: " + name);
        return "您好:" + name;
    }
}

【26】Spring声明式事务配置

Spring框架提供了声明式事务管理机制。代码(Advice通知)配置都是已定义。只要按照框架给定的固定规则,编辑配置文件,就可以增加声明式事务。
要求:
 1. 导入spring-tx.jar依赖
 2. spring配置文件增加xml namespace
 3. 按照规则配置事务管理机制。
    3.1 配置spring提供的advice通知对象。是一个标签。这个通知对象,是基于DataSource中的Connection实现的事务管理。
    3.2 配置spring提供的基于数据源的事务管理器
    3.3 把配置好的事务管理通知,织入到目标位置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd"
       default-autowire="default">

    <context:property-placeholder
            location="classpath:db/mysqlConfig.properties,classpath:config/owner.properties"></context:property-placeholder>

    <!-- 整合MyBatis -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 使用读取的属性配置文件中的内容。使用SpringEL表达式读取。 ${propertyKey} -->
        <property name="driverClassName" value="${mysql.driver}"></property>
        <property name="url" value="${mysql.url}"></property>
        <property name="username" value="${mysql.username}"></property>
        <property name="password" value="${mysql.password}"></property>
    </bean>
    
    <!-- 配置基于数据源的事务管理器bean对象
         工作内容,就是从数据源中,获取正在访问数据库的那个连接对象Connection。
     -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 配置切面
         AOP配置中,必须的配置顺序是:
          <aop:pointcut> 若干个
          <aop:advisor> 若干个
          <aop:aspect> 若干个
         当Schema-based和AspectJ混合使用的时候,一定是先运行Schema-based,再运行AspectJ,先结束AspectJ,后结束Schema-based
         上班后,开发AOP,一定是一种技术。
		先配置的先运行后结束, 后配置的后运行先结束
     -->
    <aop:config>
        <!-- 配置切面,和普通切面的配置方式一样 -->
       <aop:pointcut id="txPC" expression="execution( * com.bjsxt.service.*.* (..) )"/>
       <aop:advisor advice-ref="txAdvice" pointcut-ref="txPC"/>
    </aop:config>

    <!-- 配置spring提供的事务管理advice
         事务管理通知,是基于数据源的事务管理器,实现的事务管理。
         id - 唯一标记。后续做aop配置的时候,需要使用。
         transaction-manager - 使用的事务管理器对象。默认值是transactionManager。建议配置
     -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 配置具体的事务管理模式。
             如:管理事务的方法名是什么、事务传播行为是什么、事务的隔离级别是什么、事务是不是只读的。
         -->
        <tx:attributes>
            <!-- 配置若干method标签,描述具体的管理方案。如果有多个方法需要配置,定义若干method标签即可。
                 如果要配置的多个方法,命名有一定的规则,可以使用通配符配置。如:新增方法都是addXxx, 方法名是 add*
                 当多个标签的name,有覆盖的时候,如:有addLoginLog和add*和*,精确匹配 > 部分匹配 > 全匹配
                 name - 要管理事务的方法名字,是要和pointcut相关。
                 isolation - 事务隔离级别。
                   可选值:
                     DEFAULT - 默认值,使用数据库的默认隔离级别.MySQL 可重复读。 Oracle 读已提交
                     READ_UNCOMMITTED - 读未提交 -> 脏读 
                     READ_COMMITTED - 读已提交 -> 幻读  同样的条件下,第1次和第2次读出来的记录数不一样
                     REPEATABLE_READ - 可重复读 -> 不可重复读 同样的条件下,读取过的数据,当我们再次读取时值发生了变化
                     SERIALIZABLE - 序列化,串行化
                 propagation - 事务传播行为,
                   常用可选值:
                     REQUIRED - 必要的
                     SUPPORTS - 支持的
                 rollback-for - 当name对应的方法抛出什么类型的异常时,回滚事务。包含这个类型的子孙类型。
                 no-rollback-for - 当name对象的方法抛出什么类型的异常时,不回滚事务。包含子孙类型。
             -->
            <tx:method name="addLoginLog" isolation="DEFAULT" propagation="REQUIRED" rollback-for="java.lang.Exception"
                       no-rollback-for="java.lang.Error"/>
            <tx:method name="updateLoginLog"/>
            <tx:method name="remove*"/>
        </tx:attributes>
    </tx:advice>
    
  <!-- 配置事务通知 (通用版) -->
    <!--<tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="register" rollback-for="java.lang.Exception" isolation="DEFAULT" propagation="REQUIRED"/>
            &lt;!&ndash; read-only 只读的事务,不管提交还是回滚,对数据没有影响,使用只读事务管理。性能比普通事务高。 &ndash;&gt;
            <tx:method name="login" read-only="false"/>
            <tx:method name="add*" />
            <tx:method name="modify*" />
            <tx:method name="remove*" />
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>-->
</beans>

【27】注解配置声明式事务

注解管理事务:
 可以简化配置文件内容,提高开发效率。
 要求:
  1. 导入spring-tx.jar依赖
  2. spring配置文件增加xml namespace tx
  3. 只增加额外配置,bean对象,事务管理器
  4. 在需要管理事务的方法上,增加注解。
<!-- # 1.配置注解扫描: -->
<context:component-scan base-package="com.bjsxt.service.impl"></context:component-scan>
<!-- # 2.开启事务注解的支持: -->
<tx:annotation-driven></tx:annotation-driven>
<!-- 
# 3.必须配置事务管理器类
	如果在XML配置事务管理器时,id不叫transactionManager,需要在@Transactional(transactionManager="XML配置时id值")
 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
    /**
     * 注册
     *
     * @param users
     * @return
     * @Transactional - 事务管理的注解。直接定义,有若干的默认值。且有唯一的强制要求。
     * 强制要求: 配置文件中的事务管理器的bean唯一标记,必须是 transactionManager,大小写敏感。
     * 如果不是这个bean的唯一标记,则必须提供注解属性 transactionManager, 设置事务管理器的bean唯一标记。
     * 常用属性:
     * propagation - 传播行为,默认是REQUIRED
     * isolation - 隔离级别,默认是DEFAULT
     * rollbackFor - 是一个Class[]类型的属性,默认抛出所有异常都回滚。
     * noRollbackFor - 是一个Class[]类型的属性,默认没有忽略的异常。
     * readOnly - 是否是只读的,默认false
     */
    @Override
    @Transactional(transactionManager = "transactionManager", 
                   propagation = Propagation.REQUIRED, 
                   isolation = Isolation.DEFAULT, 
                   rollbackFor = {java.lang.Exception.class}, 
                   noRollbackFor = {}, 
                   readOnly = false)
    public int register(Users users) {
        System.out.println("代码版本是:" + version + " , 开发者 : " + owner + " , str1 = " + str1 + " , str2 = " + str2);
        int rows = usersMapper.insertUsers(users);
        return rows;
    }

【28】获取属性文件中值

    /**
     * 假设,当前类型,初始化对象的时候,有某属性需要赋值。且这个属性非常重要。还不能使用Autowire做自动注入时。
     * 如:当前类型的版本,当前类型的开发者名字等。
     * 常用于,为属性赋值。这个属性,很少变化。且不会定义在spring容器中作为bean对象存在。 软编码。
     * 使用此注解的注入,无需提供get/set方法
     */
    @Value("${owner.version}")
    private String version;
    @Value("${owner.name:也是默认值}")
    private String owner;
    @Value("直接注入的字符串")
    private String str1;
    @Value("${owner.str2:我是默认值}")
    private String str2;
# properties
owner.version=1.0
owner.name=kerwin_king
    <!-- 读取属性配置文件。 只能写一个标签。 读取多个属性配置文件,用逗号分隔。 -->
    <context:property-placeholder
            location="classpath:db/mysqlConfig.properties,classpath:config/owner.properties"></context:property-placeholder>

    <!-- 整合MyBatis -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 使用读取的属性配置文件中的内容。使用SpringEL表达式读取。 ${propertyKey} -->
        <property name="driverClassName" value="${mysql.driver}"></property>
        <property name="url" value="${mysql.url}"></property>
        <property name="username" value="${mysql.username}"></property>
        <property name="password" value="${mysql.password}"></property>
    </bean>

【30】Bean的生命周期

// todo

【31】代理模式

[1] 优点:
  1. 如果需要为被调用的目标代码(服务端代码),增减功能,可以不修改原始代码。通过代理实现增减
  2. 可以减少被调用的目标代码中的不必要逻辑。减少代码污染。
     不必要的逻辑:
       如: 业务代码(Service)中,如果需要记录日志数据。日志是业务代码么?
           如果日志代码,写在service中,那么修改日志,不修改service业务逻辑,是否需要修改代码?是否需要重新编译?

[2] 种类:
  1. 静态代理: 代理类型代码需要手工编写。
  2. 动态代理: 代理类型代码不需要手工编写,自动生成。增减的功能,需要手工编写。
     实现技术:
       1. JDK : 基于接口的实现,提供动态代理。 被代理的类型,必须实现某接口,代理类型,实现同样的接口。 注意,必须是直接实现。
       2. Cglib : 基于继承的实现,提供动态代理。被代理的类型,可以不实现接口,代理类型是被代理类型的子类型。需要导入三方jar包的。


[3] 静态代理实现:
  必须手工定义一个静态代理类型。定义方式不限。只要能够最终调用被代理的方法即可。

[4] 动态代理实现: 不需要手工定义代理类型,只需要定义要增强的功能。 这个功能,需要实现接口。
  JDK实现动态代理:基于接口
  Cglib实现动态代理:基于父类型, 必须增加cglib依赖

[5] AOP 与 代理模式
Spring的AOP是面向切面编程,定义若干通知,织入到目标对象上。最终生成的就是代理对象。
SpringAOP的底层,默认使用JDK动态代理,生成AOP Proxy代理对象。

【32】代理模式 - 静态代理

package com.bjsxt.staticproxy;

import com.bjsxt.serivice.UserService;

/**
 * 静态代理类型。
 * 目的: 一定要调用到,目标方法。也就是被代理的方法。
 */
public class MyProxy {
    // 被代理的对象。最终目标是,让对象中的方法login运行。
    private UserService userService;

    /**
     * 自定义的代理方法。为了让使用者,感觉舒服一些。代理方法定义的和目标方法一样。
     * 不一样也可以。
     */
    public void login() {
        try {
            // 前置增强  通知
            System.out.println("静态代理 - 前置");
            userService.login();
            // 后置增强  通知
            System.out.println("静态代理 - 后置");
        } catch (Exception e) {
            // 异常增强  通知
            System.out.println("静态代理 - 异常");
        }
    }

    public MyProxy() {
    }

    public MyProxy(UserService userService) {
        this.userService = userService;
    }

    public UserService getUserService() {
        return userService;
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}
package com.bjsxt.serivice.impl;

import com.bjsxt.serivice.UserService;

import java.util.Random;

public class UserServiceImpl implements UserService {
    @Override
    public void login() {
        System.out.println("UserServiceImpl.login()方法运行。。。。");
        Random random = new Random();
        if (random.nextInt(10) % 3 == 0) {
            throw new RuntimeException();
        }
    }
}
package com.bjsxt.test;

import com.bjsxt.serivice.UserService;
import com.bjsxt.serivice.impl.UserServiceImpl;
import com.bjsxt.staticproxy.MyProxy;
import org.junit.Test;

public class TestStaticProxy {
    @Test
    public void testStaticProxy() {
        // 创建目标对象
        UserService userService = new UserServiceImpl();
        // 创建静态代理对象
        MyProxy proxy = new MyProxy(userService);
        // 调用代理对象中的方法,间接调用目标方法
        for (int i = 0; i < 10; i++) {
            proxy.login();
        }
    }
}

【33】代理模式 - 动态代理(JDK)

package com.bjsxt.dynamicproxy;

import com.bjsxt.serivice.UserService;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * 定义的代理增强逻辑。
 * 需要实现接口 InvocationHandler
 */
public class MyJdkProxyHandler implements InvocationHandler {
    /**
     * 被代理的对象
     */
    private UserService target;

    /**
     * invoke方法。这个就是要增加的强化逻辑。
     * 代码中,必须指定目标方法运行。
     *
     * @param o       代理的对象
     * @param method  正在运行的,被代理的方法对象
     * @param objects 正在运行的,被代理的方法的参数表
     * @return 被代理的方法的返回值
     * @throws Throwable 被代理的方法抛出的异常,通过此定义抛出。
     */
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        try {
            // 前置通知
            System.out.println("JDK动态代理 - 前置。");
            /**
             * 在Java动态代理中,当你调用代理对象(proxy)的toString()方法时,它确实会触发InvocationHandler中的invoke()方法,
             * 因为代理对象会代理所有的方法调用,包括从Object类继承的方法,如toString(), hashCode(), 和 equals()
             * proxy是一个动态代理对象,它会将所有方法调用委托给invoke()方法处理。
             * 下面代码触发了o.toString() 从而导致递归调用invoke() 从而导致 'java.lang.StackOverflowError' 错误
             */

//            System.out.println("invoke方法参数 o = " + o + " ; 参数类型 = " + o.getClass().getName());
//            System.out.println("invoke方法参数 method.name = " + method.getName());
//            System.out.println("invoke方法参数 objects参数数组 = " + Arrays.toString(objects));

            // 让被代理的方法运行
            Object returnValue = method.invoke(target, objects);
            System.out.println("目标方法返回值 = " + returnValue);

            // 后置通知
            System.out.println("JDK动态代理 - 后置 。");
            return returnValue;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("JDK动态代理 - 异常");
            // 异常通知
            throw e;
        }
    }

    /**
     * Object Proxy.newProxyInstance(ClassLoader 类加载器, Class[] 代理要实现的接口数组, InvocationHandler 代理的增加处理器)
     * JDK生成的动态代理对象的类型,统一命名规则是 $Proxy数字。这个JDK动态代理对象,实现了被代理对象类型实现的所有接口。
     *
     * @return
     */
    public UserService getProxy() {
        // 通过JDK的Proxy类型,生成动态代理对象
        return (UserService) Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    public MyJdkProxyHandler() {
    }

    public MyJdkProxyHandler(UserService target) {
        this.target = target;
    }

    public UserService getTarget() {
        return target;
    }

    public void setTarget(UserService target) {
        this.target = target;
    }
}
package com.bjsxt.test;

import com.bjsxt.dynamicproxy.MyJdkProxyHandler;
import com.bjsxt.serivice.UserService;
import com.bjsxt.serivice.impl.UserServiceImpl;
import org.junit.Test;

import java.util.Arrays;

public class TestJDKProxy {
    @Test
    public void testJDKProxy(){
        // 创建被代理的对象
        UserService userService = new UserServiceImpl();
        // 创建InvocationHandler类型的对象
        MyJdkProxyHandler handler = new MyJdkProxyHandler(userService);
        // 基于JDK生成动态代理对象
        UserService proxy = handler.getProxy();
        // 通过代理对象,调用方法
        proxy.login();
        // 查看代理对象的具体类型
        System.out.println(proxy.getClass().getName());
        // 查看代理对象实现的接口
        System.out.println(Arrays.toString(proxy.getClass().getInterfaces()));
        // 查看代理对象类型的父类型
        System.out.println(proxy.getClass().getSuperclass().getName());
        System.out.println(proxy instanceof UserServiceImpl);
    }
}
package com.bjsxt.serivice.impl;

import com.bjsxt.serivice.UserService;

import java.util.Random;

public class UserServiceImpl implements UserService {
    @Override
    public void login() {
        System.out.println("UserServiceImpl.login()方法运行。。。。");
        Random random = new Random();
        if (random.nextInt(10) % 3 == 0) {
            throw new RuntimeException();
        }
    }
}

【34】代理模式 - 动态代理(CGLIB)

<!-- 引入依赖 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
package com.bjsxt.dynamicproxy;

import com.bjsxt.serivice.impl.UserServiceImpl;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 基于Cglib定义的动态代理增强逻辑
 * 实现接口
 */
public class MyCglibProxyHandler implements MethodInterceptor {
    /**
     * 被代理的对象。基于父类型提供代理。
     */
    private UserServiceImpl target;

    /**
     * 增强逻辑
     *
     * @param o           代理对象
     * @param method      被代理的方法
     * @param objects     被代理的方法的参数表
     * @param methodProxy 代理方法
     * @return 被代理方法的返回值
     * @throws Throwable 被代理方法抛出的异常,通过此定义传递。
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        try {
            // 前置
            System.out.println("CGLIB动态代理前置");

            // 调用被代理方法
            Object returnValue = methodProxy.invokeSuper(o, objects);

            // 后置
            System.out.println("CGLIB动态代理后置");

            return returnValue;
        } catch (Exception e) {
            // 异常
            e.printStackTrace();
            System.out.println("CGLIB动态代理异常");
            throw e;
        }
    }

    /**
     * 生成动态代理对象的方法
     */
    public UserServiceImpl getProxy() {
        // 创建CGLIB用于创建动态代理对象的工厂
        Enhancer enhancer = new Enhancer();
        // 设置父类型。也就是被代理的类型
        enhancer.setSuperclass(UserServiceImpl.class);
        // 设置增强对象
        enhancer.setCallback(this);
        // 创建动态代理对象
        return (UserServiceImpl) enhancer.create();
    }

    public MyCglibProxyHandler() {
    }

    public MyCglibProxyHandler(UserServiceImpl target) {
        this.target = target;
    }

    public UserServiceImpl getTarget() {
        return target;
    }

    public void setTarget(UserServiceImpl target) {
        this.target = target;
    }
}
package com.bjsxt.serivice.impl;

import com.bjsxt.serivice.UserService;

import java.util.Random;

public class UserServiceImpl implements UserService {
    @Override
    public void login() {
        System.out.println("UserServiceImpl.login()方法运行。。。。");
        Random random = new Random();
        if (random.nextInt(10) % 3 == 0) {
            throw new RuntimeException();
        }
    }
}

package com.bjsxt.test;

import com.bjsxt.dynamicproxy.MyCglibProxyHandler;
import com.bjsxt.serivice.impl.UserServiceImpl;
import org.junit.Test;

import java.util.Arrays;

public class TestCGLIBProxy {
    @Test
    public void test() {
        // 创建被代理的对象
        UserServiceImpl userService = new UserServiceImpl();
        // 创建代理增强对象
        MyCglibProxyHandler handler = new MyCglibProxyHandler(userService);
        // 获取动态代理对象
        UserServiceImpl proxy = handler.getProxy();
        // 调用方法
        proxy.login();
        // 查看动态代理的具体类型
        System.out.println(proxy.getClass().getName());
        // 查看动态代理对象的父类型
        System.out.println(proxy.getClass().getSuperclass().getName());
        // 查看动态代理对象实现的接口类型
        System.out.println(Arrays.toString(proxy.getClass().getInterfaces()));
    }
}

【35】基于AspectJ实现登录日志

drop table if exists tb_login_log;
create table tb_login_log(
  id int(11) primary key auto_increment,
  users_id int(11) comment '登录用户外键',
  users_username varchar(32) comment '用户名',
  remote_addr varchar(32) comment '客户端IP',
  login_date datetime comment '登录时间',
  login_result varchar(32) comment '登录结果: 成功 | 失败',
  login_cause varchar(32) comment '登录失败原因: 用户名不存在 | 密码错误 | 代码异常等。如果登录成功,当前字段为null或空字符串'
) comment '登录日志表格';
package com.bjsxt.advice;

import com.bjsxt.exception.LoginException;
import com.bjsxt.exception.PasswordCheckFailException;
import com.bjsxt.exception.PasswordNotEqException;
import com.bjsxt.exception.UsernameNotFoundException;
import com.bjsxt.mapper.LoginLogMapper;
import com.bjsxt.pojo.LoginLog;
import com.bjsxt.pojo.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * 登录方法后置通知
 */
@Component
public class LoginPostAdvice {
    @Autowired
    private LoginLogMapper loginLogMapper;

    /**
     * afterReturning - 正常结束后的后置通知
     */
    @Transactional
    public void afterReturning(Users returnValue, String username, String password, String remoteAddr) {
        // 当前方法运行,则一定登录成功。
        System.out.println("用户:" + returnValue.getName() + ",在" + new Date() + "时登录");
        LoginLog loginLog = new LoginLog();
        loginLog.setUsersId(returnValue.getId());
        loginLog.setUsersUsername(returnValue.getUsername());
        loginLog.setLoginResult("成功");
        loginLog.setLoginCause("");
        loginLog.setRemoteAddr(remoteAddr);
        loginLog.setLoginDate(new Date());

        // 记录登录日志到数据库
        System.out.println("新增日志前:" + loginLog);
        loginLogMapper.insert(loginLog);
        System.out.println("新增日志后:" + loginLog);
    }

    /**
     * afterThrowing - 抛出异常时的后置通知
     */
    @Transactional
    public void afterThrowing(LoginException e, String username, String password, String remoteAddr) {
        LoginLog loginLog = new LoginLog();
        loginLog.setUsersUsername(username);
        loginLog.setLoginDate(new Date());
        loginLog.setLoginResult("失败");
        loginLog.setRemoteAddr(remoteAddr);
        // 当前方法运行,则一定登录失败。根据异常的具体类型,决定如何处理
        if (e instanceof UsernameNotFoundException) {
            System.out.println("用户名错误");
            loginLog.setLoginCause("用户名不存在");
        } else if (e instanceof PasswordNotEqException) {
            System.out.println("密码错误");
            loginLog.setLoginCause("密码错误");
        } else if (e instanceof PasswordCheckFailException) {
            System.out.println("密码长度错误,永远不运行,前置通知抛出异常,不会触发异常通知的运行。");
        } else {
            System.out.println("代码发生异常");
            loginLog.setLoginCause("代码异常,类型是:" + e.getClass().getSimpleName());
        }
        System.out.println("新增日志前:" + loginLog);
        loginLogMapper.insert(loginLog);
        System.out.println("新增日志后:" + loginLog);
    }
}
package com.bjsxt.servlet;

import com.bjsxt.exception.LoginException;
import com.bjsxt.exception.PasswordCheckFailException;
import com.bjsxt.exception.PasswordNotEqException;
import com.bjsxt.exception.UsernameNotFoundException;
import com.bjsxt.pojo.Users;
import com.bjsxt.service.UsersService;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private UsersService usersService;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        usersService =
                WebApplicationContextUtils.findWebApplicationContext(config.getServletContext())
                        .getBean("usersService", UsersService.class);
    }

    /**
     * 登录
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 收集请求参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("客户端:" + req.getRemoteAddr());
        try {
            // 调用服务代码
            Users users = usersService.login(username, password, req.getRemoteAddr());
            // 登录成功
            req.getSession().setAttribute("loginUser", users);
            // 重定向
            resp.sendRedirect("/loginSuccess.jsp");
        }catch (UsernameNotFoundException ue){
            // 根据服务代码的结果,决定返回的视图页面。
            ue.printStackTrace();
            // 用户名不存在
            resp.sendRedirect("/loginFail.jsp");
        }catch (PasswordNotEqException pe){
            // 根据服务代码的结果,决定返回的视图页面。
            pe.printStackTrace();
            // 密码错误
            resp.sendRedirect("/loginFail.jsp");
        }catch (PasswordCheckFailException pce){
            pce.printStackTrace();
            // 密码长度校验错误
            resp.sendRedirect("/loginFail.jsp");
        }catch (LoginException le){
            // 根据服务代码的结果,决定返回的视图页面。
            le.printStackTrace();
            // 其他错误
            resp.sendRedirect("/error.jsp");
        }
    }
}
package com.bjsxt.service.impl;

import com.bjsxt.exception.LoginException;
import com.bjsxt.exception.PasswordNotEqException;
import com.bjsxt.exception.UsernameNotFoundException;
import com.bjsxt.mapper.UsersMapper;
import com.bjsxt.pojo.Users;
import com.bjsxt.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 服务实现
 */
@Service("usersService")
public class UsersServiceImpl implements UsersService {
    @Autowired
    private UsersMapper usersMapper;

    /**
     * 登录方法
     *
     * @param username 用户名
     * @param password 密码
     * @return
     * @throws LoginException
     */
    @Override
    @Transactional
    public Users login(String username, String password, String remoteAddr) throws LoginException {

        // 根据用户名查询用户
        Users users = null;
        try {
            users = usersMapper.selectByUsername(username);
        } catch (Exception e) {
            e.printStackTrace();
            throw new LoginException("用户登录异常-数据库访问错误", e);
        }

        // 判断用户是否存在
        if (users == null) {
            // 用户名不存在
            throw new UsernameNotFoundException("用户不存在");
        }

        // 判断密码是否正确
        if (!users.getPassword().equals(password)) {
            // 密码错误
            throw new PasswordNotEqException("密码错误");
        }

        // 返回

        return users;
    }
}
package com.bjsxt.mapper;

import com.bjsxt.pojo.Users;

/**
 * 数据访问接口, 持久层接口
 */
public interface UsersMapper {
    Users selectByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.bjsxt.mapper.UsersMapper">
    <select id="selectByUsername" resultType="Users">
        select id, name, username, password from tb_users where username = #{username}
    </select>
</mapper>
package com.bjsxt.mapper;

import com.bjsxt.pojo.LoginLog;

public interface LoginLogMapper {
    int insert(LoginLog loginLog);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.bjsxt.mapper.LoginLogMapper">
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into tb_login_log(id, users_id, login_result, login_cause, login_date, users_username, remote_addr)
        values(default, #{usersId}, #{loginResult}, #{loginCause}, #{loginDate}, #{usersUsername}, #{remoteAddr})
    </insert>
</mapper>
package com.bjsxt.exception;

/**
 * 自定义异常,登录异常。为了让程序员一定明确知晓这个异常的发生,不继承运行时异常,继承Exception异常。
 */
public class LoginException extends Exception {
    public LoginException() {
        super();
    }

    public LoginException(String message) {
        super(message);
    }

    public LoginException(String message, Throwable cause) {
        super(message, cause);
    }
}

package com.bjsxt.exception;

/**
 * 密码长度检查异常
 */
public class PasswordCheckFailException extends LoginException {
    public PasswordCheckFailException() {
        super();
    }

    public PasswordCheckFailException(String message) {
        super(message);
    }

    public PasswordCheckFailException(String message, Throwable cause) {
        super(message, cause);
    }
}

package com.bjsxt.exception;

/**
 * 密码错误异常, 登录异常是子异常
 */
public class PasswordNotEqException extends LoginException {
    public PasswordNotEqException() {
        super();
    }

    public PasswordNotEqException(String message) {
        super(message);
    }

    public PasswordNotEqException(String message, Throwable cause) {
        super(message, cause);
    }
}

package com.bjsxt.exception;

/**
 * 登录异常的子异常,用户名找不到。
 */
public class UsernameNotFoundException extends LoginException {
    public UsernameNotFoundException() {
        super();
    }

    public UsernameNotFoundException(String message) {
        super(message);
    }

    public UsernameNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

package com.bjsxt.pojo;

import java.io.Serializable;
import java.util.Date;
import java.util.Objects;

public class LoginLog implements Serializable {
    private Integer id;
    private Integer usersId;
    private String loginResult;
    private String loginCause;
    private String usersUsername;
    private String remoteAddr;
    private Date loginDate;

    public LoginLog() {
    }
    // get set 方法省略
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值