一篇文章让你彻底掌握Spring所有知识点

目录

1、Spring简介  

1.1、Spring概述

1.2、Spring家族

1.3、Spring Framework

1.3.1、Spring Framework特性

1.3.2、Spring Framework五大功能模块

2、IOC

2.1、IOC容器

2.1.1、IOC思想

2.1.2、IOC容器在Spring中的实现

2.2、基于XML管理bean

2.2.1、入门案例

2.2.2、获取bean 

2.2.3、依赖注入之setter注入

2.2.4、依赖注入之构造器注入

2.2.5、特殊值处理

2.2.6、为类类型属性赋值

2.2.7、为数组类型属性赋值

2.2.8、为集合类型属性赋值

2.2.9、p命名空间

2.2.10、引入外部属性文件(与数据库进行连接)

2.2.11、bean的作用域

2.2.12、bean的生命周期

2.2.13、FactoryBean

2.2.14、基于xml的自动装配

2.3、基于注解管理bean

2.3.1、标记与扫描

 2.3.2、基于注解的自动装配

 3、AOP

3.1、场景模拟

3.1.1、声明接口

3.1.2、创建实现类

3.1.3、创建带日志功能的实现类

3.1.4、提出问题

3.2、代理模式

3.2.1、概念

3.2.2、静态代理

3.2.3、动态代理

3.2.4、测试

3.3、AOP概念及相关术语

3.3.1、概述

3.3.2、相关术语

3.3.3、作用

3.4、基于注解的AOP

3.4.1、技术说明

3.4.2、准备工作

3.4.3、创建切面类并配置

3.4.4、各种通知

 3.4.5、切入点表达式语法

3.4.6、重用切入点表达式

3.4.7、获取通知的相关信息

3.4.8、环绕通知

3.4.9、切面的优先级

3.5、基于XML的AOP(了解)

3.5.1、准备工作

3.5.2、实现

SSM系列的Spring全部知识点已杀青!后续会有SpringMVC和SpringBoot知识点总结,大家尽请期待~~


1Spring简介  

1.1Spring概述

官网地址: https://spring.io/

Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用

Spring 框架来创建性能好、易于测试、可重用的代码。

Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首

            次在 Apache 2.0 许可下发布。

Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。

Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应

           用程序是需要扩展的。

Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO编程模型来促进良好的                编程实践。

1.2Spring家族

项目列表: https://spring.io/projects

1.3Spring Framework

        Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework为基础的。

1.3.1Spring Framework特性

  • 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常 小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
  • 控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
  • 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
  • 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
  • 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
  • 一站式:在 IOC AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。

1.3.2Spring Framework五大功能模块

   功能模块
功能介绍
Core Container
核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
AOP&Aspects
面向切面编程
Testing
提供了对 junit TestNG 测试框架的整合。
Data Access/Integration
提供了对数据访问 / 集成的功能。
Spring MVC
提供了面向 Web 应用程序的集成功能。

2IOC

2.1IOC容器

2.1.1IOC思想

IOCInversion of Control,翻译过来是反转控制

1、获取资源的传统方式

        自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握。

        在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。

2、反转控制方式获取资源

        点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。

        反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

3、DI

DIDependency Injection,翻译过来是依赖注入

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

所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。

2.1.2IOC容器在Spring中的实现

        Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:

1、BeanFactory

        这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。(我们用不了)

2、ApplicationContext

        BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory

3、ApplicationContext的主要实现类

类型名
简介
ClassPathXmlApplicationContext
通过读取类路径下的 XML 格式的配置文件创建 IOC 容器
对象
FileSystemXmlApplicationContext
通过文件系统路径读取 XML 格式的配置文件创建 IOC
器对象
ConfigurableApplicationContext
ApplicationContext 的子接口,包含一些扩展方法
refresh() close() ,让 ApplicationContext 具有启动、
关闭和刷新上下文的能力。
WebApplicationContext
专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对
象,并将对象引入存入 ServletContext 域中。

2.2、基于XML管理bean

2.2.1、入门案例

1、创建Maven Module
2、引入依赖
<dependencies>
    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

 3、创建类HelloWorld

public class HelloWorld {
    public void sayHello(){
        System.out.println("helloworld");
    }
}
4、创建Spring的配置文件

 5、Spring的配置文件中配置bean

<!--
    配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
    通过bean标签配置IOC容器所管理的bean
    属性:
        id:设置bean的唯一标识
        class:设置bean所对应类型的全类名
-->
<bean id="helloworld" class="com.songqiao.spring.bean.HelloWorld"></bean>
6、创建测试类测试
@Test
public void testHelloWorld(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");   
    HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
    helloworld.sayHello();
}
7、思路

 8、注意

        Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要无参构造器时,没有无参构造器,则会抛出下面的异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name

'helloworld' defined in class path resource [applicationContext.xml]: Instantiation of bean

failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed

to instantiate [com.atguigu.spring.bean.HelloWorld]: No default constructor found; nested

exception is java.lang.NoSuchMethodException: com.atguigu.spring.bean.HelloWorld.<init>

()

2.2.2、获取bean 

①方式一:根据 id 获取

由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。

上个实验中我们使用的就是这种方式。

②方式二:根据类型获取
@Test
public void testHelloWorld(){
    ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
    HelloWorld bean = ac.getBean(HelloWorld.class);
    bean.sayHello();
}
③方式三:根据 id 和类型
@Test
public void testHelloWorld(){
    ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
    HelloWorld bean = ac.getBean("helloworld", HelloWorld.class);
    bean.sayHello();
}
④注意

当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个

IOC容器中一共配置了两个:

<bean id="helloworldOne" class="com.songqiao.spring.bean.HelloWorld"></bean>
<bean id="helloworldTwo" class="com.songqiao.spring.bean.HelloWorld"></bean>
根据类型获取时会抛出异常:
org.springframework.beans.factory. NoUniqueBeanDefinitionException : No qualifying bean
of type 'com.atguigu.spring.bean.HelloWorld' available: expected single matching bean but
found 2: helloworldOne,helloworldTwo
⑤扩展
如果组件类实现了接口,根据接口类型可以获取 bean 吗?
可以,前提是 bean 唯一

 如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

不行,因为 bean 不唯一
⑥结论

 根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类

型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。 

2.2.3、依赖注入之setter注入

①创建学生类 Student
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String sex;

    public Student() {

    }
    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 Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Student{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", age=" + age +
        ", sex='" + sex + '\'' +
        '}';
    }
}
②配置 bean 时为属性赋值
<bean id="studentOne" class="com.songqiao.spring.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
-->
    <!-- value属性:指定属性值 -->
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="23"></property>
    <property name="sex" value="男"></property>
</bean>
③测试
@Test
public void testDIBySet(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
Student studentOne = ac.getBean("studentOne", Student.class);
System.out.println(studentOne);
}

2.2.4、依赖注入之构造器注入

①在 Student 类中添加有参构造
public Student(Integer id, String name, Integer age, String sex) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.sex = sex;
}
②配置 bean
<bean id="studentTwo" class="com.songqiao.spring.bean.Student">
    <constructor-arg value="1002"></constructor-arg>
    <constructor-arg value="李四"></constructor-arg>
    <constructor-arg value="33"></constructor-arg>
    <constructor-arg value="女"></constructor-arg>
</bean>
注意:
constructor-arg 标签还有两个属性可以进一步描述构造器参数:
index 属性:指定参数所在位置的索引(从 0 开始)
name 属性:指定参数名

③测试

@Test
public void testDIBySet(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
Student studentOne = ac.getBean("studentTwo", Student.class);
System.out.println(studentOne);
}

2.2.5、特殊值处理

①字面量赋值
什么是字面量?
int a = 10;
声明一个变量 a ,初始化为 10 ,此时 a 就不代表字母 a 了,而是作为一个变量的名字。当我们引用 a的时候,我们实际上拿到的值是10
而如果 a 是带引号的: 'a' ,那么它现在不是一个变量,它就是代表 a 这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>
null
null值不可以写在property的value中,会被当成字符串并赋值
<property name="name">
    <null />
</property>
xml 实体
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a &lt; b"/>
CDATA
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
    <value><![CDATA[a < b]]></value>
</property>

2.2.6、为类类型属性赋值

①创建班级类 Clazz
public class Clazz {
    private Integer clazzId;
    private String clazzName;
    public Integer getClazzId() {
        return clazzId;
    }
    public void setClazzId(Integer clazzId) {
        this.clazzId = clazzId;
    }
    public String getClazzName() {
        return clazzName;
    }
    public void setClazzName(String clazzName) {
        this.clazzName = clazzName;
    }
    @Override
    public String toString() {
        return "Clazz{" +
            "clazzId=" + clazzId +
            ", clazzName='" + clazzName + '\'' +
        '}';
    }
    public Clazz() {
    }
    public Clazz(Integer clazzId, String clazzName) {
        this.clazzId = clazzId;
        this.clazzName = clazzName;
    }
}
②修改 Student
Student 类中添加以下代码:
private Clazz clazz;
public Clazz getClazz() {
    return clazz;
}
public void setClazz(Clazz clazz) {
    this.clazz = clazz;
}
③方式一:引用外部已声明的 bean
配置 Clazz 类型的 bean
<bean id="clazzOne" class="com.songqiao.spring.bean.Clazz">
    <property name="clazzId" value="1111"></property>
    <property name="clazzName" value="财源滚滚班"></property>
</bean>
Student 中的 clazz 属性赋值:
<bean id="studentFour" class="com.songqiao.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
</bean>
错误演示:
<bean id="studentFour" class="com.songqiao.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
    <property name="clazz" value="clazzOne"></property>
</bean>
如果错把 ref 属性写成了 value 属性,会抛出异常: Caused by: java.lang. IllegalStateException :Cannot convert value of type 'java.lang.String' to required type'com.atguigu.spring.bean.Clazz' for property 'clazz': no matching editors or conversion
strategy found
意思是不能把 String 类型转换成我们要的 Clazz 类型,说明我们使用 value 属性时, Spring 只把这个属性看做一个普通的字符串,不会认为这是一个bean id ,更不会根据它去找到 bean 来赋值

④方式二:内部bean

<bean id="studentFour" class="com.songqiao.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
    <property name="clazz">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
        <bean id="clazzInner" class="com.atguigu.spring.bean.Clazz">
            <property name="clazzId" value="2222"></property>
            <property name="clazzName" value="远大前程班"></property>
        </bean>
    </property>
</bean>

 ③方式三:级联属性赋值

<bean id="studentFour" class="com.songqiao.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
    <!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="clazz.clazzId" value="3333"></property>
    <property name="clazz.clazzName" value="最强王者班"></property>
</bean>

2.2.7、为数组类型属性赋值

①修改 Student
Student 类中添加以下代码:
private String[] hobbies;
public String[] getHobbies() {
    return hobbies;
}
public void setHobbies(String[] hobbies) {
    this.hobbies = hobbies;
}
②配置 bean
<bean id="studentFour" class="com.songqiao.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="hobbies">
        <array>
            <value>唱</value>
            <value>跳</value>
            <value>rap</value>
            <value>篮球</value>
        </array>
    </property>
</bean>

2.2.8、为集合类型属性赋值

①为 List 集合类型属性赋值
Clazz 类中添加以下代码:
private List<Student> students;
public List<Student> getStudents() {
    return students;
}
public void setStudents(List<Student> students) {
    this.students = students;
}
配置 bean :(提前定义好三个不同学生的bean)
<bean id="clazzTwo" class="com.songqiao.spring.bean.Clazz">
    <property name="clazzId" value="4444"></property>
    <property name="clazzName" value="Javaee0222"></property>
    <property name="students">
        <list>
            <ref bean="studentOne"></ref>
            <ref bean="studentTwo"></ref>
            <ref bean="studentThree"></ref>
        </list>
    </property>
</bean>
若为 Set 集合类型属性赋值,只需要将其中的 list 标签改为 set 标签即可

②为Map集合类型属性赋值

创建教师类Teacher

public class Teacher {
    private Integer teacherId;
    private String teacherName;
    public Integer getTeacherId() {
        return teacherId;
    }
    public void setTeacherId(Integer teacherId) {
        this.teacherId = teacherId;
    }
    public String getTeacherName() {
        return teacherName;
    }
    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }
    public Teacher(Integer teacherId, String teacherName) {
        this.teacherId = teacherId;
        this.teacherName = teacherName;
    }
    public Teacher() {
    }
    @Override
    public String toString() {
        return "Teacher{" +
            "teacherId=" + teacherId +
            ", teacherName='" + teacherName + '\'' +
            '}';
    }
}

 Student类中添加以下代码:

private Map<String, Teacher> teacherMap;
public Map<String, Teacher> getTeacherMap() {
    return teacherMap;
}
public void setTeacherMap(Map<String, Teacher> teacherMap) {
    this.teacherMap = teacherMap;
}
配置 bean

一、第一种方式 使用ref引用一个map的bean 其中引用了teacher的bean

<bean id="teacherOne" class="com.songqiao.spring.bean.Teacher">
    <property name="teacherId" value="10010"></property>
    <property name="teacherName" value="大宝"></property>
</bean>
<bean id="teacherTwo" class="com.songqiao.spring.bean.Teacher">
    <property name="teacherId" value="10086"></property>
    <property name="teacherName" value="二宝"></property>
</bean>
<bean id="studentFour" class="com.songqiao.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="hobbies">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
    <property name="teacherMap">
        <map>
            <entry>
                <key>
                    <value>10010</value>
                </key>
                <ref bean="teacherOne"></ref>
            </entry>
            <entry>
                <key>
                    <value>10086</value>
                </key>
                <ref bean="teacherTwo"></ref>
            </entry>
        </map>
   </property>
</bean>

二、第二种方式 使用map标签 再去引用我们提前设置好的教师bean

        <property name="teacherMap"  ref="teacherMap">

        </property>

    <!--   配置一个Map集合类型的bean,需要使用util的约束 -->
    <util:map id="teacherMap">
        <entry key="1" value-ref="teacherOne"></entry>
        <entry key="2" value-ref="teacherTwo"></entry>
    </util:map>

    <bean id="teacherOne" class="com.songqiao.spring.pojo.Teacher">
        <property name="tId" value="1"/>
        <property name="tName" value="谷德丽"></property>
    </bean>

    <bean id="teacherTwo" class="com.songqiao.spring.pojo.Teacher">
        <property name="tId" value="2"/>
        <property name="tName" value="张聪"></property>
    </bean>
③引用集合类型的 bean
    <property name="students" ref="studentList"></property>

    <!--   配置一个list集合类型的bean,需要使用util的约束 -->
    <util:list id="studentList">
        <ref bean="studentThree"></ref>
        <ref bean="studentFour"></ref>
    </util:list>
使用 util:list util:map 标签必须引入相应的命名空间,可以通过 idea 的提示功能选择

2.2.9p命名空间

引入 p 命名空间后,可以通过以下方式为 bean 的各个属性赋值
    <bean id="studentSix" class="com.songqiao.spring.pojo.Student"
    p:age="23" p:gender="男" p:sname="龙城桥少" p:teacherMap-ref="teacherMap">
        <property name="hobby">
            <array>
                <value>打飞机</value>
                <value>造火箭</value>
            </array>
        </property>
    </bean>

2.2.10、引入外部属性文件(与数据库进行连接)

①加入依赖
<!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
<!-- 数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.31</version>
</dependency>
②创建外部属性文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/ssm?useSSL=false
jdbc.username=root
jdbc.password=123456
③引入属性文件
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
④配置 bean
<!--    把德鲁伊作为bean交给ioc管理,因为datasource是接口 bean是对象 所以使用datasource的实现类德鲁伊-->
    <bean id="DataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
⑤测试
@Test
public void testDataSource() throws SQLException {
    ApplicationContext ac = new ClassPathXmlApplicationContext("springdatasource.xml");
    DataSource dataSource = ac.getBean(DataSource.class);
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
}

⑥结果

2.2.11bean的作用域

①概念
Spring 中可以通过配置 bean 标签的 scope 属性来指定 bean 的作用域范围,各取值含义参加下表:
  取值 含义  创建对象的时机
singleton (默认单例)
IOC 容器中,这个 bean 的对象始终为单实例
  IOC 容器初始化时
prototype (多例)
这个 bean IOC 容器中有多个实例
  获取 bean
如果是在 WebApplicationContext 环境下还会有另外两个作用域(但不常用):
取值
含义
request
在一个请求范围内有效
session
在一个会话范围内有效
②创建类 User
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    public User() {
    }
    public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer 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;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", age=" + age +
            '}';
    }
}
③配置 bean
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建
对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<bean class="com.songqiao.bean.User" scope="prototype"></bean>
④测试
@Test
public void testBeanScope(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-scope.xml");
    User user1 = ac.getBean(User.class);
    User user2 = ac.getBean(User.class);
    System.out.println(user1==user2);
}

2.2.12bean的生命周期

①具体的生命周期过程
  • bean对象创建(调用无参构造器,实例化bean)
  • bean对象设置属性(依赖注入)
  • bean对象初始化之前操作(由bean的后置处理器负责)
  • bean对象初始化(需在配置bean时指定初始化方法)
  • bean对象初始化之后操作(由bean的后置处理器负责)
  • IOC容器关闭 bean对象销毁(需在配置bean时指定销毁方法)
②修改类 User
package com.songqiao.spring.pojo;

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;

    public User() {
        System.out.println("生命周期1:实例化bean");
    }
    public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        System.out.println("生命周期2:依赖注入");
        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;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
    public void initMethod(){
        System.out.println("生命周期3:初始化bean");
    }
    public void destroyMethod(){
        System.out.println("生命周期4:销毁");
    }
}
注意其中的 initMethod() destroyMethod() ,可以通过配置 bean 指定为初始化和销毁的方法

 ③配置bean

<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.songqiao.bean.User" scope="prototype" init-method="initMethod"
destroy-method="destroyMethod">
    <property name="id" value="1001"></property>
    <property name="username" value="admin"></property>
    <property name="password" value="123456"></property>
    <property name="age" value="23"></property>
</bean>
④测试
@Test
public void testLife(){
    ClassPathXmlApplicationContext ioc = new
ClassPathXmlApplicationContext("spring-lifecycle.xml");
    User bean = ioc.getBean(User.class);
    System.out.println(bean);
    ioc.close();
}
bean 的后置处理器
bean 的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现 BeanPostProcessor 接口,且配置到IOC 容器中,需要注意的是, bean 后置处理器不是单独针对某一个 bean 生效,而是针对 IOC 容器中所有bean 都会执行。
创建 bean 的后置处理器:
//spring的后置处理器
//快捷键拓展:ctrl+O可以重写方法
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化前执行
        System.out.println("MyBeanPostProcessor-->后置处理器的postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化后执行
        System.out.println("MyBeanPostProcessor-->后置处理器的postProcessAfterInitialization");
        return bean;
    }
}
IOC 容器中配置后置处理器:
<!--   后置处理器会针对IOC容器中每一个bean都起作用-->
    <bean id="myBeanPostProcessor" class="com.songqiao.spring.process.MyBeanPostProcessor"></bean>

2.2.13FactoryBean

①简介

FactoryBeanSpring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by objects used within a {@link BeanFactory}
which
* are themselves factories for individual objects. If a bean implements this
* interface, it is used as a factory for an object to expose, not directly as a
* bean instance that will be exposed itself.
*
* <p><b>NB: A bean that implements this interface cannot be used as a normal
bean.</b>
* A FactoryBean is defined in a bean style, but the object exposed for bean
* references ({@link #getObject()}) is always the object that it creates.
*
* <p>FactoryBeans can support singletons and prototypes, and can either create
* objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}
* interface allows for exposing more fine-grained behavioral metadata.
*
* <p>This interface is heavily used within the framework itself, for example
for
* the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
* {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
* custom components as well; however, this is only common for infrastructure
code.
*
* <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not
* supposed to rely on annotation-driven injection or other reflective
facilities.</b>
* {@link #getObjectType()} {@link #getObject()} invocations may arrive early in
the
* bootstrap process, even ahead of any post-processor setup. If you need access
to
* other beans, implement {@link BeanFactoryAware} and obtain them
programmatically.
*
* <p><b>The container is only responsible for managing the lifecycle of the
FactoryBean
* instance, not the lifecycle of the objects created by the FactoryBean.</b>
Therefore,
* a destroy method on an exposed bean object (such as {@link
java.io.Closeable#close()}
更多Java –大数据 – 前端 – UI/UE - Android - 人工智能资料下载,可访问百度:尚硅谷官网(www.atguigu.com)
* will <i>not</i> be called automatically. Instead, a FactoryBean should
implement
* {@link DisposableBean} and delegate any such close call to the underlying
object.
*
* <p>Finally, FactoryBean objects participate in the containing BeanFactory's
* synchronization of bean creation. There is usually no need for internal
* synchronization other than for purposes of lazy initialization within the
* FactoryBean itself (or the like).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 08.03.2003
* @param <T> the bean type
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.aop.framework.ProxyFactoryBean
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
public interface FactoryBean<T> {
/**
* The name of an attribute that can be
* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
* factory beans can signal their object type when it can't be deduced from
* the factory bean class.
* @since 5.2
*/
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* <p>As with a {@link BeanFactory}, this allows support for both the
* Singleton and Prototype design pattern.
* <p>If this FactoryBean is not fully initialized yet at the time of
* the call (for example because it is involved in a circular reference),
* throw a corresponding {@link FactoryBeanNotInitializedException}.
* <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
* objects. The factory will consider this as normal value to be used; it
* will not throw a FactoryBeanNotInitializedException in this case anymore.
* FactoryBean implementations are encouraged to throw
* FactoryBeanNotInitializedException themselves now, as appropriate.
* @return an instance of the bean (can be {@code null})
* @throws Exception in case of creation errors
* @see FactoryBeanNotInitializedException
*/
@Nullable
T getObject() throws Exception;
/**
* Return the type of object that this FactoryBean creates,
* or {@code null} if not known in advance.
* <p>This allows one to check for specific types of beans without
* instantiating objects, for example on autowiring.
* <p>In the case of implementations that are creating a singleton object,
* this method should try to avoid singleton creation as far as possible;
* it should rather estimate the type in advance.
更多Java –大数据 – 前端 – UI/UE - Android - 人工智能资料下载,可访问百度:尚硅谷官网(www.atguigu.com)
②创建类UserFactoryBean
* For prototypes, returning a meaningful type here is advisable too.
* <p>This method can be called <i>before</i> this FactoryBean has
* been fully initialized. It must not rely on state created during
* initialization; of course, it can still use such state if available.
* <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
* {@code null} here. Therefore it is highly recommended to implement
* this method properly, using the current state of the FactoryBean.
* @return the type of object that this FactoryBean creates,
* or {@code null} if not known at the time of the call
* @see ListableBeanFactory#getBeansOfType
*/
@Nullable
Class<?> getObjectType();
/**
* Is the object managed by this factory a singleton? That is,
* will {@link #getObject()} always return the same object
* (a reference that can be cached)?
* <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,
* the object returned from {@code getObject()} might get cached
* by the owning BeanFactory. Hence, do not return {@code true}
* unless the FactoryBean always exposes the same reference.
* <p>The singleton status of the FactoryBean itself will generally
* be provided by the owning BeanFactory; usually, it has to be
* defined as singleton there.
* <p><b>NOTE:</b> This method returning {@code false} does not
* necessarily indicate that returned objects are independent instances.
* An implementation of the extended {@link SmartFactoryBean} interface
* may explicitly indicate independent instances through its
* {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}
* implementations which do not implement this extended interface are
* simply assumed to always return independent instances if the
* {@code isSingleton()} implementation returns {@code false}.
* <p>The default implementation returns {@code true}, since a
* {@code FactoryBean} typically manages a singleton instance.
* @return whether the exposed object is a singleton
* @see #getObject()
* @see SmartFactoryBean#isPrototype()
*/
default boolean isSingleton() {
    return true;
    }
}
②创建类 UserFactoryBean
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
    return new User();
}
@Override
public Class<?> getObjectType() {
    return User.class;
}
}
③配置 bean
<bean id="user" class="com.songqiao.bean.UserFactoryBean"></bean>
④测试
@Test
public void testUserFactoryBean(){
//获取IOC容器
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-factorybean.xml");
    User user = (User) ac.getBean("user");
    System.out.println(user);
}

2.2.14、基于xml的自动装配

自动装配:
根据指定的策略,在 IOC 容器中匹配某一个 bean ,自动为指定的 bean 中所依赖的类类型或接口类型属性赋值
①场景模拟
创建类 UserController
public class UserController {
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void saveUser(){
        userService.saveUser();
    }
}
创建接口 UserService
public interface UserService {
    void saveUser();
}
创建类 UserServiceImpl 实现接口 UserService
public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}
创建接口 UserDao
public interface UserDao {
    void saveUser();
}
创建类 UserDaoImpl 实现接口 UserDao
public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("保存成功");
    }
}
②配置 bean
使用 bean 标签的 autowire 属性设置自动装配效果
自动装配方式: byType
byType :根据类型匹配 IOC 容器中的某个兼容类型的 bean ,为属性自动赋值
若在 IOC 中,没有任何一个兼容类型的 bean 能够为属性赋值,则该属性不装配,即值为默认值 null 若在IOC 中,有多个兼容类型的 bean 能够为属性赋值,则抛出异常
NoUniqueBeanDefinitionException
    <bean id="userController" class="com.songqiao.spring.controller.UserController" autowire="byType" >
<!--        <property name="userService" ref="userService"></property>-->
    </bean>

    <bean id="userService" class="com.songqiao.spring.service.impl.UserServiceImpl" autowire="byType">
<!--        <property name="userDao" ref="userDao"></property>-->
    </bean>

    <bean id="userDao" class="com.songqiao.spring.dao.impl.UserDaoImpl"></bean>
自动装配方式: byName
byName :将自动装配的属性的属性名,作为 bean id IOC 容器中匹配相对应的 bean 进行赋值
<bean id="userController"
    class="com.songqiao.autowire.xml.controller.UserController" autowire="byName">
</bean>
<bean id="userService"
    class="com.songqiao.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userServiceImpl"
    class="com.songqiao.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userDao" class="com.songqiao.autowire.xml.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.songqiao.autowire.xml.dao.impl.UserDaoImpl">
</bean>
③测试
@Test
public void testAutoWireByXML(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("autowire-xml.xml");
    UserController userController = ioc.getBean(UserController.class);
    userController.saveUser();
}

2.3、基于注解管理bean

2.3.1、标记与扫描

①注解

XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴上气球。

班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面同学们做的工作,相当于框架的具体操作。

②扫描

Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

③新建Maven Module

<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
<!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
④创建 Spring 配置文件

⑤标识组件的常用注解

@Component :将类标识为普通组件 @Controller :将类标识为控制层组件 @Service :将类标识为业务层组件 @Repository :将类标识为持久层组件
问:以上四个注解有什么关系和区别?

        通过查看源码我们得知,@Controller@Service@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。

        对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller@Service@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

        注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。

⑥创建组件
创建控制层组件
@Controller
public class UserController {
}
创建接口 UserService
public interface UserService {
}
创建业务层组件 UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
}
创建接口 UserDao
public interface UserDao {
}
创建持久层组件 UserDaoImpl
@Repository
public class UserDaoImpl implements UserDao {
}
⑦扫描组件
情况一:最基本的扫描方式
<context:component-scan base-package="com.songqiao">
</context:component-scan>
情况二:指定要排除的组件
<context:component-scan base-package="com.atguigu">
    <!-- context:exclude-filter标签:指定排除规则 -->
    <!--
        type:设置排除或包含的依据
        type="annotation",根据注解排除,expression中设置要排除的注解的全类名
        type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
    <!--<context:exclude-filter type="assignable"
expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>
情况三:仅扫描指定组件
<context:component-scan base-package="com.atguigu" use-default-filters="false">
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类         -->
    <!--
        type:设置排除或包含的依据
        type="annotation",根据注解排除,expression中设置要排除的注解的全类名
        type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
    <!--<context:include-filter type="assignable"
expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>
⑧测试
@Test
public void testAutowireByAnnotation(){
    ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
    UserController userController = ac.getBean(UserController.class);
    System.out.println(userController);
    UserService userService = ac.getBean(UserService.class);
    System.out.println(userService);
    UserDao userDao = ac.getBean(UserDao.class);
    System.out.println(userDao);
}
⑨组件所对应的 bean id
在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
默认情况
类名首字母小写就是 bean id 。例如: UserController 类对应的 bean id 就是 userController
自定义 bean id可通过标识组件的注解的value 属性设置自定义的 bean id
@Service("userService")// 默认为 userServiceImpl public class UserServiceImpl implements
UserService {}

 2.3.2、基于注解的自动装配

①场景模拟

参考基于 xml 的自动装配
UserController 中声明 UserService 对象
UserServiceImpl 中声明 UserDao 对象

@Autowired注解

在成员变量上直接标记 @Autowired 注解即可完成自动装配,不需要提供 setXxx() 方法。以后我们在项目中的正式用法就是这样。
@Controller
public class UserController {

    @Autowired
    private UserService userService;

    public void saveUser(){
        userService.saveUser();
    }
}
public interface UserService {
    void saveUser();
}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}
public interface UserDao {
    void saveUser();
}
@Repository
public class UserDaoImpl implements UserDao {

    @Override
    public void saveUser() {
        System.out.println("保存成功");
    }
}
@Autowired 注解其他细节
@Autowired 注解可以标记在构造器和 set 方法上
@Controller
public class UserController {
    private UserService userService;
    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }
    public void saveUser(){
        userService.saveUser();
    }
}
@Controller
public class UserController {
    private UserService userService;
    @Autowired
    public void setUserService(UserService userService){
        this.userService = userService;
    }
    public void saveUser(){
        userService.saveUser();
    }
}
@Autowired 工作流程:装配策略:byType-->byName

首先根据所需要的组件类型到IOC容器中查找

        1、能够找到唯一的bean:直接执行装配

        2、如果完全找不到匹配这个类型的bean:装配失败

        3、和所需类型匹配的bean不止一个

                没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为beanid进行

                匹配

                        1、能够找到:执行装配

                        2、找不到:装配失败

                使用@Qualifier注解:根据@Qualifier注解中指定的名称作为beanid进行匹配

                        1、能够找到:执行装配

                        2、找不到:装配失败

@Controller
public class UserController {
    @Autowired
    @Qualifier("userServiceImpl")
    private UserService userService;

    public void saveUser(){
        userService.saveUser();
    }
}
@Autowired 中有属性 required ,默认值为 true ,因此在自动装配无法找到相应的 bean 时,会装配失败
可以将属性 required 的值设置为 true ,则表示能装就装,装不上就不装,此时自动装配的属性为默认值
但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。

 3AOP

3.1、场景模拟

3.1.1、声明接口

声明计算器接口 Calculator ,包含加减乘除的抽象方法
public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

3.1.2、创建实现类

public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
    int result = i + j;
    System.out.println("方法内部 result = " + result);
    return result;
}
@Override
public int sub(int i, int j) {
    int result = i - j;
    System.out.println("方法内部 result = " + result);
    return result;
}
@Override
public int mul(int i, int j) {
    int result = i * j;
    System.out.println("方法内部 result = " + result);
    return result;
}
@Override
public int div(int i, int j) {
    int result = i / j;
    System.out.println("方法内部 result = " + result);
    return result;
}
}

3.1.3、创建带日志功能的实现类

public class CalculatorLogImpl implements Calculator {
@Override
public int add(int i, int j) {
    System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
    int result = i + j;
    System.out.println("方法内部 result = " + result);
    System.out.println("[日志] add 方法结束了,结果是:" + result);
    return result;
}
@Override
public int sub(int i, int j) {
    System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
    int result = i - j;
    System.out.println("方法内部 result = " + result);
    System.out.println("[日志] sub 方法结束了,结果是:" + result);
    return result;
}
@Override
public int mul(int i, int j) {
    System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
    int result = i * j;
    System.out.println("方法内部 result = " + result);
    System.out.println("[日志] mul 方法结束了,结果是:" + result);
    return result;
}
@Override
public int div(int i, int j) {
    System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
    int result = i / j;
    System.out.println("方法内部 result = " + result);
    System.out.println("[日志] div 方法结束了,结果是:" + result);
    return result;
}
}

3.1.4、提出问题

①现有代码缺陷
针对带日志功能的实现类,我们发现有如下缺陷:
  • 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
  • 附加功能分散在各个业务功能方法中,不利于统一维护
②解决思路
解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。
③困难
解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。

3.2、代理模式

3.2.1、概念

①介绍

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

 使用代理后:

②生活中的代理
  • 广告商找大明星拍广告需要经过经纪人
  • 合作伙伴找大老板谈合作要约见面时间需要经过秘书
  • 房产中介是买卖双方的代理
③相关术语
  • 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
  • 目标:被代理套用了非核心逻辑代码的类、对象、方法。

3.2.2、静态代理

创建静态代理类:
public class CalculatorStaticProxy implements Calculator {
// 将被代理的目标对象声明为成员变量
    private Calculator target;
    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }
    @Override
    public int add(int i, int j) {
// 附加功能由代理类中的代理方法来实现
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
// 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
        System.out.println("[日志] add 方法结束了,结果是:" + addResult);
        return addResult;
    }
}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

3.2.3、动态代理

 生产代理对象的工厂类:

public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxy(){
/**
* newProxyInstance():创建一个代理实例
* 其中有三个参数:
* 1、classLoader:加载动态生成的代理类的类加载器
* 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
* 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接
口中的抽象方法
*/
    ClassLoader classLoader = target.getClass().getClassLoader();
    Class<?>[] interfaces = target.getClass().getInterfaces();
    InvocationHandler invocationHandler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
/**
* proxy:代理对象
* method:代理对象需要实现的方法,即其中需要重写的方法
* args:method所对应方法的参数
*/
    Object result = null;
    try {
        System.out.println("[动态代理][日志] "+method.getName()+",参
数:"+ Arrays.toString(args));
        result = method.invoke(target, args);
        System.out.println("[动态代理][日志] "+method.getName()+",结
果:"+ result);
} catch (Exception e) {
        e.printStackTrace();
        System.out.println("[动态代理][日志] "+method.getName()+",异
常:"+e.getMessage());
} finally {
        System.out.println("[动态代理][日志] "+method.getName()+",方法
执行完毕");
}
        return result;
    }
};
    return Proxy.newProxyInstance(classLoader, interfaces,
invocationHandler);
    }
}

3.2.4、测试

@Test
public void testDynamicProxy(){
    ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
    Calculator proxy = (Calculator) factory.getProxy();
    proxy.div(1,0);
    //proxy.div(1,1);
}

3.3AOP概念及相关术语

3.3.1、概述

AOPAspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

3.3.2、相关术语

①横切关注点(在切面中叫做通知)

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

②通知(在目标方法中叫横切关注点)
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

③切面
封装通知方法的类。

④目标
被代理的目标对象。
⑤代理
向目标对象应用通知之后创建的代理对象。
⑥连接点(需要功能增强的位置)

这也是一个纯逻辑概念,不是语法定义的。

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。

⑦切入点

定位连接点的方式。

每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。

如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。

Spring AOP 技术可以通过切入点定位到特定的连接点。

切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条

件。

3.3.3、作用

简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,

提高内聚性。

代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就

被切面给增强了。

3.4、基于注解的AOP

3.4.1、技术说明

动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因

为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。

cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。

AspectJ:本质上是静态代理,将代理逻辑织入被代理的目标类编译得到的字节码文件,所以最

终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

3.4.2、准备工作

①添加依赖
IOC 所需依赖基础上再加入下面依赖即可:
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>
②准备被代理的目标资源
接口:
public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
实现类:
@Component
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {

        int result = i + j;
        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int sub(int i, int j) {

        int result = i - j;
        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int mul(int i, int j) {

        int result = i * j;
        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

3.4.3、创建切面类并配置

//切面
//在切面中 需要通过指定的注解将方法标识为通知方法
//@Before:前置通知,在目标对象方法执行之前执行
//通过JoinPoint可以获取连接点所对应的方法名和参数
//    重用切入点表达式
//    @Pointcut("execution(* com.songqiao.spring.aop.annotation.*.*(..))")
//    public void PointCut(){}
//@AfterReturning:返回通知,在目标对象方法返回值之后执行
//@After:后置通知,在目标对象方法的finally子句中执行的
//@AfterThrowing:异常通知,在目标对象方法的catch子句中执行

@Component
@Aspect //将当前组件标识为切面
public class LoggerAspect {

    //设置统一的切入点表达式
    @Pointcut("execution(* com.songqiao.spring.aop.annotation.*.*(..))")
    public void PointCut(){}

    //前置通知
    // @Before("execution(public int com.songqiao.spring.aop.annotation.CalculatorImpl.add(int,int))")
    // @Before("execution(* com.songqiao.spring.aop.annotation.*.*(..))")
    @Before("PointCut()")
    public void beforeAdviceMethod(JoinPoint joinPoint){
        //获取连接点的签名信息
        Signature signature = joinPoint.getSignature();
        //获取连接点所对应方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect,前置通知"+"  方法名:"+signature.getName()+"  所对应的参数:"+ Arrays.toString(args));
    }

    @After("PointCut()")
    public void afterAdviceMethod(JoinPoint joinPoint){
        //获取连接点的签名信息
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect,后置通知:方法:"+signature.getName()+",执行完毕");
    }
    /**
     * 在返回通知中若要获取目标方法的返回值
     * 只需要通过@AfterReturning注解的returning属性
     * 就可以将通知方法的某个参数指定为目标方法的返回值的参数
     * 保证注解中的returning属性与通知方法参数一致即可获取目标方法的返回值
     * */
    @AfterReturning(value = "PointCut()",returning = "result")
    public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect:返回通知:方法"+signature.getName()+"结果:"+result);
    }
    //获取异常信息与 返回通知一致
    @AfterThrowing(value = "PointCut()",throwing = "ex")
    public void afterThrowingAdviceMethod(JoinPoint joinPoint,Exception ex){
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect,异常通知:方法"+signature.getName()+"异常信息:"+ex);
    }

    //环绕通知 相当于动态代理
    @Around("PointCut()")
    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
        Object result=null;
        try {
            //前置通知位置
            System.out.println("环绕的前置通知");
            //相当于是目标对象方法的执行
             result = joinPoint.proceed();
            //返回通知位置
            System.out.println("环绕的返回通知");
        } catch (Throwable throwable) {
            //异常通知位置
            throwable.printStackTrace();
            System.out.println("环绕的异常通知");
        }finally {
            //后置通知位置
            System.out.println("环绕的后置通知");
        }
        return result;
    }
}
Spring 的配置文件中配置:
<!--
基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<context:component-scan base-package="com.songqiao.aop.annotation">
</context:component-scan>
<aop:aspectj-autoproxy />

3.4.4、各种通知

  • 前置通知:使用@Before注解标识,在被代理的目标方法执行
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
各种通知的执行顺序:
Spring 版本 5.3.x 以前:
        前置通知
        目标操作
        后置通知
        返回通知或异常通知
Spring 版本 5.3.x 以后:
        前置通知
        目标操作
        返回通知或异常通知
        后置通知

 3.4.5、切入点表达式语法

①作用

②语法细节
  • *号代替权限修饰符返回值部分表示权限修饰符返回值不限
  • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
        例如:*.Hello 匹配 com.Hello ,不匹配 com.atguigu.Hello
  • 在包名的部分,使用“*..”表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分
        例如:*Service 匹配所有名称以 Service 结尾的类或接口
  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分
        例如:*Operation 匹配所有方法名以 Operation 结尾的方法
  • 在方法参数列表部分,使用(..)表示参数列表任意
  • 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
        切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
        例如:execution(public int .. Service.*(.., int)) 正确
        例如:execution(* int .. Service.*(.., int)) 错误
图示:

3.4.6、重用切入点表达式

 ①声明

@Pointcut("execution(* com.songqiao.aop.annotation.*.*(..))")
public void pointCut(){}
②在同一个切面中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
③在不同切面中使用
@Before("com.songqiao.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

3.4.7、获取通知的相关信息

①获取连接点信息
获取连接点信息可以在通知方法的参数位置设置 JoinPoint 类型的形参
@Before("execution(public int com.songqiao.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
    //获取连接点的签名信息
    String methodName = joinPoint.getSignature().getName();
    //获取目标方法到的实参信息
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
②获取目标方法的返回值
@AfterReturning 中的属性 returning ,用来将通知方法的某个形参,接收目标方法的返回值
@AfterReturning(value = "execution(* com.songqiao.aop.annotation.CalculatorImpl.*
(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}
③获取目标方法的异常
@AfterThrowing 中的属性 throwing ,用来将通知方法的某个形参,接收目标方法的异常
@AfterThrowing(value = "execution(* com.songqiao.aop.annotation.CalculatorImpl.*
(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

3.4.8、环绕通知

//环绕通知 相当于动态代理
    @Around("PointCut()")
    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
        Object result=null;
        try {
            //前置通知位置
            System.out.println("环绕的前置通知");
            //相当于是目标对象方法的执行
             result = joinPoint.proceed();
            //返回通知位置
            System.out.println("环绕的返回通知");
        } catch (Throwable throwable) {
            //异常通知位置
            throwable.printStackTrace();
            System.out.println("环绕的异常通知");
        }finally {
            //后置通知位置
            System.out.println("环绕的后置通知");
        }
        return result;
    }

3.4.9、切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的 内外嵌套 顺序。
  • 优先级高的切面:外面
  • 优先级低的切面:里面
使用 @Order 注解可以控制切面的优先级:
  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

3.5、基于XMLAOP(了解)

3.5.1、准备工作

参考基于注解的 AOP 环境

3.5.2、实现

<context:component-scan base-package="com.songqiao.aop.xml"></context:component-scan>
<aop:config>
    <!--配置切面类-->
    <aop:aspect ref="loggerAspect">
        <aop:pointcut id="pointCut" expression="execution(*
com.songqiao.aop.xml.CalculatorImpl.*(..))"/>
        <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
        <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
        <aop:after-returning method="afterReturningMethod" returning="result"
pointcut-ref="pointCut"></aop:after-returning>
        <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
        <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
    </aop:aspect>
    <aop:aspect ref="validateAspect" order="1">
        <aop:before method="validateBeforeMethod" pointcut-ref="pointCut">
</aop:before>
    </aop:aspect>
</aop:config>

SSM系列的Spring全部知识点已杀青!后续会有SpringMVC和SpringBoot知识点总结,大家尽请期待~~

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙城桥少

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值