SSM【定制化】~ Spring

一、概述

1.1、Spring是什么?

  • Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。

  • Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。

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

  • Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践。

1.2、Spring 的优良特性

  1. 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
  2. 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
  3. 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
  4. 面向切面编程:Aspect Oriented Programming——AOP
  5. 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
  6. 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
  7. 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)

1.3、使用 Spring 框架的好处

Spring 框架主要的好处:

  1. Spring 可以使开发人员使用 POJOs 开发企业级的应用程序。只使用 POJOs 的好处是你不需要一个 EJB 容器产品,比如一个应用程序服务器,但是你可以选择使用一个健壮的 servlet 容器,比如 Tomcat 或者一些商业产品。
  2. Spring 在一个单元模式中是有组织的。即使包和类的数量非常大,你只要担心你需要的,而其它的就可以忽略了。
  3. Spring 不会让你白费力气做重复工作,它真正的利用了一些现有的技术,像 ORM 框架、日志框架、JEE、Quartz 和 JDK 计时器,其他视图技术。
  4. 测试一个用 Spring 编写的应用程序很容易,因为环境相关的代码被移动到这个框架中。此外,通过使用 JavaBean-style POJOs,它在使用依赖注入注入测试数据时变得更容易。
  5. Spring 的 web 框架是一个设计良好的 web MVC 框架,它为比如 Structs 或者其他工程上的或者不怎么受欢迎的 web 框架提供了一个很好的供替代的选择。MVC 模式导致应用程序的不同方面(输入逻辑,业务逻辑和UI逻辑)分离,同时提供这些元素之间的松散耦合。模型(Model)封装了应用程序数据,通常它们将由 POJO 类组成。视图(View)负责渲染模型数据,一般来说它生成客户端浏览器可以解释 HTML 输出。控制器(Controller)负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染。
  6. Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
  7. 轻量级的 IOC 容器往往是轻量级的,例如,特别是当与 EJB 容器相比的时候。这有利于在内存和 CPU 资源有限的计算机上开发和部署应用程序。
  8. Spring 提供了一致的事务管理接口,可向下扩展到(使用一个单一的数据库,例如)本地事务并扩展到全局事务(例如,使用 JTA)。

1.4、依赖注入DI(重点)

  • Spring 最认同的技术是控制反转的**依赖注入(DI)**模式。控制反转(IOC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。

  • 当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能的独立于其他的 Java 类来增加这些类可重用可能性,当进行单元测试时,可以使它们独立于其他类进行测试。依赖注入(或者有时被称为配线)有助于将这些类粘合在一起,并且在同一时间让它们保持独立。

  • 到底什么是依赖注入?让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类 A 依赖于类 B。现在,让我们看看第二部分,注入。所有这一切都意味着类 B 将通过 IoC 被注入到类 A 中。

  • 依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。由于依赖注入是 Spring 框架的核心部分,所以我将在一个单独的章节中利用很好的例子去解释这一概念。

1.5、面向切面的程序设计AOP(重点)

  • Spring 框架的一个关键组件是**面向切面的程序设计(AOP)框架。一个程序中跨越多个点的功能被称为横切关注点**,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样常见的很好的关于方面的例子,比如日志记录、声明性事务、安全性,和缓存等等。

  • 在 OOP 中模块化的关键单元是类,而在 AOP 中模块化的关键单元是方面。AOP 帮助你将横切关注点从它们所影响的对象中分离出来,然而依赖注入帮助你将你的应用程序对象从彼此中分离出来。

  • Spring 框架的 AOP 模块提

  • 供了面向方面的程序设计实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于 .Net 属性的方式合并行为信息到代码中。我将在一个独立的章节中讨论更多关于 Spring AOP 的概念。

二、Spring IOC(重点)

2.1、Spring ApplicationContext 容器

  • Application Context 是 BeanFactory 的子接口,也被称为 Spring 上下文。

  • Application Context 是 Spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。

  • ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。

  • 最常被使用的 ApplicationContext 接口实现

    • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。

    • ClassPathXmlApplicationContext(推荐使用):该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件

    • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

  • ClassPathXmlApplicationContext:使用案例

    1、创建maven项目

在这里插入图片描述

2、引入Spring依赖

<!-- Spring 依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.15.RELEASE</version>
</dependency>

3、创建测试对象类

public class Person
{
    private Long id;
    private String name;
    private String sex;
    private Integer age;
    
    //...在这省略...(getter/setter/有参无参/tostring)
}

4、创建beans.xml配置文件(Spring核心配置文件)

  • resources目录下创建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
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- 默认通过空构造方法创建 bean(Spring帮我们 new 对象) -->
    <bean id="person" class="com.quite.domain.Person"/>

</beans>

5、创建测试类

public static void main(String[] args)
{
    //1、创建ApplicationContext容器
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    //2、通过byName获取Spring容器中的bean(person)
    Person person = (Person) context.getBean("person");

    //3、打印输出看结果
    System.out.println(person);
}

测试结果:

在这里插入图片描述

总结:从该案例可以发现,我们不需要自己创建对象,而Spring已经帮我们完成了,这就是依赖注入(DI)。

2.2、Spring bean 定义(常用几种)

注意在Spring核心配置文件加载,Spring容器管理的对象(bean)就已经初始化(创建了)了。

2.2.1、无参构造

<!-- 无参构造方法创建对象 -->
<bean id="person01" class="com.ruanjia.entity.Person"/>

2.2.2、有参构造

<!-- 有参构造方法创建对象 -->
<bean id="person02" class="com.ruanjia.entity.Person">
    <constructor-arg name="id" value="10001" />
    <constructor-arg name="name" value="ruanjia01" />
    <constructor-arg name="sex" value="0" />
    <constructor-arg name="age" value="23" />
</bean>

2.2.3、SET方法

<!-- 通过set方法创建对象 -->
<bean id="person03" class="com.ruanjia.entity.Person">
    <property name="id" value="10002" />
    <property name="name" value="ruanjia02" />
    <property name="sex" value="1" />
    <property name="age" value="23" />
</bean>

2.2.4、测试

public class BeanMain01
{
    public static void main(String[] args)
    {
        //获取Spring上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        //无参构造new
        Person person01 = (Person) context.getBean("person01");
        //有参构造new
        Person person02 = (Person) context.getBean("person02");
        //set方法new
        Person person03 = (Person) context.getBean("person03");
        
        //打印输出
        System.out.println(person01);
        System.out.println(person02);
        System.out.println(person03);
    }
}

测试结果

在这里插入图片描述

2.3、Spring 标签(配置文件常用标签)

2.3.1、<Alias>

作用:给Spring容器中已经存在的bean起一个别名。

使用方式:在Spring核心文件中通过<Alias name="" alias=""/>标签起别名,name:是bean的名称,alias:是该bean别名

<alias name="person03" alias="per03"/>

测试:

public class BeanMain02
{
    public static void main(String[] args)
    {
        //获取Spring上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        Person per03 = (Person) context.getBean("per03");
        System.out.println(per03);
    }
}

测试结果:

在这里插入图片描述

2.3.2、<bean>

作用:把一个类交给Spring管理(由Spring帮我们创建该对象)。

使用方式:在Spring核心文件中通过<bean id="" class="" name=""/>标签,把该类交给Spring容器管理。

<!--
        1、id:bean的唯一标识
        2、class:指定创建bean对象
        3、name:可以这个bean对象起多个名字(使用 , 分割或使用 空格 分割)
    -->
<bean id="person04" class="com.ruanjia.entity.Person" name="p1,p2 p3"/>

2.3.3、<import>

作用:在某个配置文件中引用其他配置文件,把多个配置文件内容关联起来。

使用方式:在Spring核心文件中通过<import resource=""/>标签,引用其他配置文件。

<import resource="beans01.xml"/>
<import resource="beans02.xml"/>

2.4、依赖注入(DI)

依赖:bean 对象的创建依赖与 Spring 容器(由Spring容器创建)

注入:bean 对象的所有属性由 Spring 容器注入(可以通过set方法、构造方法注入属性)

2.4.1、构造器注入

<!-- 默认通过空构造方法创建 bean 没有给属性注入值 -->
<bean id="person05" class="com.ruanjia.entity.Person"/>

<!--  通过有参构造创建 bean 并且给属性注入值 -->
<bean id="person06" class="com.ruanjia.entity.Person">
    <constructor-arg name="id" value="10001" />
    <constructor-arg name="name" value="ruanjia01" />
    <constructor-arg name="sex" value="0" />
    <constructor-arg name="age" value="23" />
</bean>

测试:

public class BeanMain04
{
    public static void main(String[] args)
    {
        //获取Spring上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        Person person05 = (Person) context.getBean("person05");
        Person person06 = (Person) context.getBean("person06");
        System.out.println(person05);
        System.out.println(person06);
    }
}

测试结果:
在这里插入图片描述

2.4.2、setter方法注入(重点)

1、创建项目

在这里插入图片描述

2、创建测试对象类

public class Address 
{
    private String address;
    
    //...get/set/tostring/有/无参构造
}

public class Student
{
    private String name;
    private String[] books;
    private Address address;
    private List<String> hobbys;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties properties;

    //...get/set/tostring/有/无参构造
}

3、beans.xml

<?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
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- 由spring容器创建Address对象(bean) -->
    <bean id="address" class="com.quite.domain.Address">
        <!-- 通过set方法注入属性 -->
        <property name="address" value="广西"/>
    </bean>

    <bean id="student" class="com.quite.domain.Student">
        <!-- 普通属性注入 -->
        <property name="name" value="蔡蔡蔡"/>

        <!--  bean对象注入 ref指定bean  -->
        <property name="address" ref="address"/>

        <!--  数组注入  -->
        <property name="books">
            <array>
                <value>Spring 5 核心原理</value>
                <value>Spring 实战</value>
                <value>Spring Cloud 原理</value>
            </array>
        </property>

        <!--  map注入  -->
        <property name="card">
            <map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
                <entry key="key3" value="value3"/>
            </map>
        </property>

        <!--  set注入  -->
        <property name="games">
            <set>
                <value>王者荣耀</value>
                <value>刺激战场</value>
                <value>天天飞车</value>
            </set>
        </property>

        <!--  list注入  -->
        <property name="hobbys">
            <list>
                <value>打蓝球</value>
                <value>敲代码</value>
                <value></value>
            </list>
        </property>

        <!--  null值注入  -->
        <property name="wife">
            <null/>
        </property>

        <!-- properties 注入-->
        <property name="properties">
            <props>
                <prop key="邮箱">quite@qq.com</prop>
                <prop key="QQ">481266251</prop>
                <prop key="电话">1337725xxxx</prop>
            </props>
        </property>
    </bean>

</beans>

4、测试

1、创建MainApp.java

public class MainApp
{
    public static void main(String[] args) 
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student);
    }
}

2、测试结果

总结依赖注入就是bean对象的创建依赖于spring容器,通过setter方法注入属性值或通过构造方法注属性值。

2.4.3、p命名 和 c命名注入

使用方式:在Spring核心配置文件头部,添加命名空间约束。

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

<?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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!--  p命名空间注入,直接注入属性值:property  -->
    <bean name="user01" class="com.quite.domain.User" p:username="88888" p:password="admin"/>
    <!--  c命名空间注入,通过构造器注入:construct-args  -->
    <bean name="user02" class="com.quite.domain.User" c:username="admin" c:password="admin"/>

</beans>

测试:

public class MainApp1 
{
    public static void main(String[] args)
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
        User user01 = (User) context.getBean("user01");
        User user02 = (User) context.getBean("user02");
        System.out.println(user01);
        System.out.println(user02);
    }
}

测试结果:

在这里插入图片描述

2.5、Spring Bean 作用域

  1. 当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton
  2. Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下所示,
作用域描述
singleton在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值(重点)
prototype每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()(重点)
request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境

2.5.1、singleton(默认的bean作用域)

作用singleton 作用域每次返回都是同一个bean对象。

1、创建一个bean

<bean id="person07" class="com.ruanjia.entity.Person" scope="singleton">
    <constructor-arg name="id" value="10001" />
    <constructor-arg name="name" value="ruanjia01" />
    <constructor-arg name="sex" value="0" />
    <constructor-arg name="age" value="23" />
</bean>

2、测试

public class BeanMain05
{
    public static void main(String[] args)
    {
        //获取Spring上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        Person person1 = (Person) context.getBean("person07");
        Person person2 = (Person) context.getBean("person07");

        System.out.println(person1 == person2);
    }
}

测试结果:

在这里插入图片描述

2.5.2、prototype

作用每次从容中取出的Bean都是一个新的Bean实例。

1、创建一个bean

<bean id="person08" class="com.ruanjia.entity.Person" scope="prototype">
    <constructor-arg name="id" value="10001" />
    <constructor-arg name="name" value="ruanjia01" />
    <constructor-arg name="sex" value="0" />
    <constructor-arg name="age" value="23" />
</bean>

2、测试

public class BeanMain06
{
    public static void main(String[] args)
    {
        //获取Spring上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        Person person1 = (Person) context.getBean("person08");
        Person person2 = (Person) context.getBean("person08");

        System.out.println(person1 == person2);
    }
}

测试结果:
在这里插入图片描述

三、Spring自动装配(重点)

  • 自动装配自动装配就是指 Spring 容器在不使用 <constructor-arg> 和<property> 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。
  • 使用自动装配需要配置 <bean> 元素的 autowire 属性。
  • autowire属性有五个值,具体说明如下所示:
名称说明
no(默认值)默认值,表示不使用自动装配,Bean 依赖必须通过 ref 元素定义。
byName根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。(表示按属性名称自动装配,XML 文件中 Bean 的 id 必须与类中的属性名称相同)
byType根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。(XML 文件中 Bean 的 id 与类中的属性名称可以不同,但必须只有一个类型的 Bean,否则出现异常。)
constructor类似于 byType,根据构造方法参数的数据类型,进行 byType 模式的自动装配。(类中构造函数的参数必须在配置文件中有相同的类型)
autodetect(3.0版本不支持)如果 Bean 中有默认的构造方法,则用 constructor 模式,否则用 byType 模式。

3.1、byName(按名称装配)

  • byName:按照容器中的 bean 实例 id/name 自动装配bean,通过 setter() 方法自动装配,如果Spring容器中没有找到对应的bean会装配失败。
  • 注意通过setter方法自动装配(例如:Dog 小写 dog 为 beanName)。

测试案例

  • 一个人(Person)有两个宠物,Dog、Cat。

步骤一:创建Dog.class

public class Dog
{
    static
    {
        System.out.println("小狗.");
    }
}

步骤二:创建Cat.class

public class Cat
{
    static
    {
        System.out.println("小猫.");
    }
}

步骤三:创建Persion.class

public class Person
{
    private String name;
    private Dog test;
    private Cat cat;

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

    //byName模式:通过set方法自动装配bean(Dog小写形式dog为beanName,然后根据这个benaName(dog)去Spring容器中找对应的bean完成自动装配)
    public void setDog(Dog test) {
        this.test = test;
    }

    //byName模式:通过set方法自动装配bean
    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", dog=" + test +
                ", cat=" + cat +
                '}';
    }
}

步骤四:beans.xml注册这三个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
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- 定义两个bean -->
    <bean id="dog" class="com.ruanjia.Dog" />
    <bean id="cat" class="com.ruanjia.Cat" />

    <!-- autowire="byName":通过setter方法自动装配bean -->
    <bean class="com.ruanjia.Person" autowire="byName">
        <property name="name" value="my_test." />
    </bean>

</beans>

步骤五:BeanMain01.class

public class BeanMain01
{
    public static void main(String[] args)
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Person person = context.getBean(Person.class);
        System.out.println(person);
    }
}

运行结果:
在这里插入图片描述

3.2、byType(按类型装配)

  • byName:按属性类型装配,按类型自动装配bean,如果Spring容器中存在多个中类型的bean就会出现异常。
  • 注意通过setter方法自动装配(例如:Dog 小写 dog 为 beanName)。

测试案例

  • 一个人(Person)有两个宠物,Dog、Cat。

步骤一:创建Dog.class

public class Dog
{
    static
    {
        System.out.println("小狗.");
    }
}

步骤二:创建Cat.class

public class Cat
{
    static
    {
        System.out.println("小猫.");
    }
}

步骤三:创建Persion.class

public class Person
{
    private String name;
    private Dog dog;
    private Cat cat;
    
    public void setName(String name) {
        this.name = name;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

步骤四:beans.xml注册这三个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
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean class="com.ruanjia.Dog" />
    <bean class="com.ruanjia.Cat" />

    <!-- autowire="byType":通过setter方法的属性类型自动装配 -->
    <bean class="com.ruanjia.Person" autowire="byType">
        <property name="name" value="my_test." />
    </bean>

</beans>

步骤五:BeanMain01.class

public class BeanMain01
{
    public static void main(String[] args)
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Person person = context.getBean(Person.class);
        System.out.println(person);

    }
}

测试结果:

在这里插入图片描述

3.3、自动装配(注解版)

3.3.1、开启Spring注解支持

1、添加context约束支持,在头部beans标签内添加以下约束

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd"

2、开启Spring注解支持

<!-- 开启bean注解支持 -->
<context:annotation-config/>

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启bean注解支持 -->
    <context:annotation-config/>

</beans>

3.3.2、@Autowired(按类型自动装配)

作用:按byType自动装配,如果找不到对应类型的bean或者找到多同类型bean会出现异常。

注意@Autowired可以标注在属性上、方法上和构造器上,来完成自动装配(默认根据类型自动装配)。

public class Person
{
    private String name;
    private Dog dog;
    private Cat cat;

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

    @Autowired
    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Autowired
    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

3.3.3、@Resource(重点)

原理@Resource默认按byName自动装配,例如,Spring容器中存在多个同类型的bean时,我们使用@resource时没有指定name这时就会根据属性名或者方法属性名设为bean的名称,如果找不到这个名称的bean就会根据byType自动装配,但是Spring容器中存在多个同样类型的bean时就会出现异常。

  1. 默认按byName自动装配
  2. 如果在Spring容器中找不到该name的bean时,就根据byType自动装配(唯一的)
  3. 如果找不到指定name的bean,有找不到唯一类型的bean时就会报错!

注意@Resource可以标注在属性上、方法上和构造器上,来完成自动装配(默认根据类型自动装配)。

public class Person
{
    private String name;
    private Dog dog;
    private Cat cat;

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

    //按类型自动装配(该类型在Spring中是唯一的,否则会出现异常)
    @Resource
    public void setDog(Dog dog) {
        this.dog = dog;
    }

    //根据名称自动装配(Spring容器中存在多个同类型的bean时,可以通过name属性来完成bean自动装配)
    @Resource(name = "cat")
    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

3.3.4、@Autowired配合@Qualifier(“”)(按名称自动装配)

作用:类似@resource(name =“”)通过byName自动装配。

public class Person
{
    private String name;
    private Dog dog;
    private Cat cat;

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

    @Autowired
    @Qualifier("dog")
    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Autowired
    @Qualifier("cat")
    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

四、Spring Bean生命周期(重点)

  • Bena生命周期实例化 -> 属性注入值 -> 初始化 -> 使用 -> 销毁。

在这里插入图片描述

4.1、Spring Bean可分为四个阶段

  • 实例化(Instantiation)
  • 属性赋值(Populate)
  • 初始化(Initialization)
  • 销毁(Destruction)

4.2、BeanDefinition(bean)

  • Bean 就是 BeanDefinition
  • Spring 容器启动过程中会把 Bean 解析成 BeanDefinition。
  • 最终 Bean 工厂会这个 BeanDefinition 生产一个 Bean 实例。
  • BeanDefinition 里面里面包含了 bean 定义的各种信息。

4.3、总结

  • Bean的生命周期大致可以分为四个阶段实例化、属性赋值、初始化、销毁。

4.4、Bean生命周期博大精深,建议去看看下面文章

  • bean就是人的一生。

文章地址

https://blog.csdn.net/sinat_40770656/article/details/123498761?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165787691216781647529427%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165787691216781647529427&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-11-123498761-null-null.142^v32^control,185^v2^control&utm_term=spring%20bean%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&spm=1018.2226.3001.4187

4.5、Bean 生命周期方法

4.5.1、XML版
  • 自定义初始化方法添加“init-method”属性,Spring则会在创建对象之后,调用此方法。
  • 自定义销毁方法添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
  • 销毁工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
  • 分类Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。
public class MyBeanService
{
    //bean 生命周期初始化方法(初始化执行)
    public void init()
    {
        System.out.println("Bean 初始化.");
    }

    //bean 生命周期销毁方法(销毁前执行)
    public void destroy()
    {
        System.out.println("Bean  销毁.");
    }
}

使用生命周期方法:

  • init-method:初始化执行
  • destroy-method:销毁前执行
<bean id="myBeanService" class="com.ruanjia.service.MyBeanService" init-method="init" destroy-method="destroy" />
4.5.2、配置类版
@Configuration
public class SpringConfig 
{
	//在此定义bean的初始化及销毁方法
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public MyBeanService myBeanService() {
        return new MyBeanService();
    }
}

4.5.3、注解版

public class MyBeanService
{
    public MyBeanService()
    {
        System.out.println("MyBeanService 构造器.");
    }

    //bean 生命周期初始化方法(初始化执行)
    @PostConstruct
    public void init()
    {
        System.out.println("Bean 初始化.");
    }

    //bean 生命周期销毁方法(销毁前执行)
    @PreDestroy
    public void destroy()
    {
        System.out.println("Bean  销毁.");
    }
}

五、Spring AOP(面向切面编程)

  • AOP的实现有AspectJ、JDK动态代理、CGLIB动态代理。
  • AOP底层是采用 JDK/CGLIB动态代理实现的。

5.1、什么是 Spring AOP?

  • Aspect Oriented Programming(面向切面编程)。
  • AOP是OOP的延续,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

5.2、代理模式(动态代理、静态代理)

  • 学习AOP之前先了解以下代理模式。

5.2.1、静态代理

  • 静态代理:静态代理说白了就是在程序运行前就已经确定好了的代理对象,代理类和原始类的关系在运行前就已经确定。

  • 静态代理实现条件:

    1. 目标类具有接口,并实现了其接口。
    2. 代理类也得实现目标类的接口,并有一个属性是目标类接口。
    3. 代理类的得有一个无参构造方法和一个构造方法,参数为目标类接口类型,用于接收目标对象赋值给代理类的目标类接口属性
    4. 代理类必须实现接口的所有方法,并在在方法中访问目标类对象的方法,在访问之前和之后都可以进行一些代理操作。
  • 缺点

    1. 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
    2. 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,【静态代理】在程序规模稍大时就会难以维护。
    3. 静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。
  • 优点

    1. 静态代理对客户(测试类)隐藏了被代理类接口(目标类接口)的具体实现类,在一定程度上实现了解耦合,同时提高了安全性!

案例讲解

1、创建UserService接口(委托类和代理类统一实现该接口)

//委托类和代理类统一实现该接口
public interface UserService
{
    void addUser();
    void updateUser();
    void deleteUser();
    void queryUser();
}

2、创建委托类 UserServiceImpl 实现 UserService 接口(被代理的类)

public class UserServiceImpl implements UserService
{
    @Override
    public void addUser() {
        System.out.println("添加用户.");
    }

    @Override
    public void updateUser() {
        System.out.println("更新用户.");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户.");
    }

    @Override
    public void queryUser() {
        System.out.println("查询用户.");
    }
}

3、创建代理类(帮别人做某些事情)

public class UserServiceImplProxy implements UserService
{

    //注入UserService
    private UserService userService;

    /**
     * 通过setter方法注入委托对象
     * @param userService
     */
    public void setUserService(UserService userService)
    {
        this.userService = userService;
    }

    /**
     * 通过构造方法注入委托对象
     */
    public UserServiceImplProxy(UserService userService)
    {
        this.userService = userService;
    }

    public UserServiceImplProxy()
    {
    }

    //重写下面4个方法:这里就是通过代理的方式为这4个方法做一些处理,例如记录方法执行日志信息等...
    @Override
    public void addUser() {
        log("addUser执行了.");
        userService.addUser();
    }

    @Override
    public void updateUser() {
        log("updateUser方法执行了.");
        userService.updateUser();
    }

    @Override
    public void deleteUser() {
        log("deleteUser方法执行了.");
        userService.deleteUser();
    }

    @Override
    public void queryUser() {
        log("queryUser方法执行了.");
        userService.queryUser();
    }

    private void log(String logName)
    {
        System.out.println(logName);
    }
}

4、测试类

public class MainProxy
{
    public static void main(String[] args)
    {
        //1、由代理对象帮我们操作委托对象。
        UserService proxy = new UserServiceImplProxy(new UserServiceImpl());

        //2、通过代理调用委托对象方法
        proxy.queryUser();
        System.out.println("-------------------");
        proxy.addUser();
        System.out.println("-------------------");
        proxy.updateUser();
        System.out.println("-------------------");
        proxy.deleteUser();
    }
}

运行结果:

总结想要做的事情交给代理对象帮助我们完成即可.

5.2.2、动态代理(重点)

  • 动态代理模式动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。
  • AOP底层就是采用动态代理实现。
1、动态代理实现的条件
  • 动态代理能够实现代理类无需和被代理类直接关联,但是**动态代理类必须实现Invocation接口,并且实现invoke() 方法,在invoke()方法中需要完成两件事情:一添加服务,二调用业务逻辑方法**。
  • 代理服务就是在代理类中的invoke中执行的。
  • 我们可以通过**反射机制获取目标对象的加载类、接口,还有实现了Invocation接口的代理类传到Proxy.newProxyInstance(被代理类,被代理类接口,代理类)方法**中获取到代理类的对象实例。
2、动态代理实现过程(代码实现)

2.1、创建委托类接口 UserService

public interface UserService 
{
    void addUser();
    void updateUser();
    void deleteUser();
    void queryUser();
}

2.2、创建委托类 UserServiceImpl 实现 UserService接口

public class UserServiceImpl implements UserService
{
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

    @Override
    public void updateUser() {
        System.out.println("更新用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }

    @Override
    public void queryUser() {
        System.out.println("查询用户");
    }
}

3、创建动态代理类(重点)

public class DynamicProxyHandler implements InvocationHandler
{

    /*  目标对象(委托类)*/
    private Object target;

    /**
     * 我们可以通过DynamicProxyHandler代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,
     * 那么所有的函数调用最终都会经过 invoke 函数的转发,因此我们就可以在这里做一些自己想做的操作,
     * 比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。
     *
     * @param proxy 目标对象
     * @param method 调用方法
     * @param args 参数列表
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        Object result=null; //返回结果
        try{
            //原对象方法调用前处理日志信息
            System.out.println("---调用目标方法前记录日志---");

            //target(委托代理对象),args(目标方法的参数)
            result=method.invoke(target, args); //调用目标方法
            
            //原对象方法调用后处理日志信息
            System.out.println("---调用目标方法后记录日志---");
        }catch(Exception e){
            e.printStackTrace();
            /*  异常日志打印*/
            System.out.println("---error---");
            throw e;
        }
        return result;
    }

    /**
     * 动态生成的代理实例
     * @param targetObject 委托代理对象(UserServiceImpl)
     * @return
     */
    public Object newProxyInstance(Object targetObject)
    {
        this.target = targetObject;
        /**
         * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
         * 第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
         * 第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
         * 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
         * 根据传入的目标返回一个代理对象
         */
        return Proxy.newProxyInstance(
            targetObject.getClass().getClassLoader(), //获取委托代理对象的类加载器
                targetObject.getClass().getInterfaces(), //获取委托对象的实现接口
                this //得到InvocationHandler接口的子类实现(DynamicProxyHandler)
        );
    }
}

4、测试

public class MainProxy02
{
    public static void main(String[] args)
    {
        DynamicProxyHandler handler = new DynamicProxyHandler();
        UserService userService = (UserService) handler.newProxyInstance(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
    }
}

测试结果:

在这里插入图片描述

3、动态代理优缺点

优点

  • 动态代理实现了只需要将被代理对象作为参数传入代理类就可以获取代理类对象,从而实现类代理,具有较强的灵活性。
  • 动态代理的服务内容不需要像静态代理一样写在每个代码块中,只需要写在invoke()方法中即可,降低了代码的冗余度。

缺点

  • 动态代理类仍然需要实现接口。
4、动态代理总结
  • 根据静态代理的使用,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。
  • 在静态代理中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。
  • 在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持。
  • java.lang.reflect.InvocationHandler接口中的 invoke 定义:
    1. Object proxy:被代理的对象.
    2. Method method:要调用的目标方法.
    3. Object[] args:方法调用时所需要参数.
  • java.lang.reflect.Proxy类的 newProxyInstance 方法:
    1. CLassLoader loader:类的加载器
    2. Class<?> interfaces:获取全部的接口
    3. InvocationHandler:获取InvocationHandler接口的子类的实例

5.2.3、JDK / CGLIB

  • Spring底层包含了jdk代理和cglib代理两种动态代理生成机制.
  • 基本规则是目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理.
1、JDK动态代理
  • 基于接口进行代理。
//目标
final OrderService os = new OrderServiceImpl();
//额外功能
InvocationHandler handler = new InvocationHandler(){//1.设置回调函数(额外功能代码)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
        System.out.println("start...");
        method.invoke(os, args);
         System.out.println("end...");
        return null;
    }
};
//2.创建动态代理类
Object proxyObj = Proxy.newProxyInstance(ClassLoader , Interfaces , InvocationHandler);
2、CGLib动态代理
  • 基于继承代理。
final OrderService os = new OrderServiceImpl();
Enhancer cnh = new Enhancer();//1.创建字节码曾强对象
enh.setSuperclass(os.getClass());//2.设置父类(等价于实现原始类接口)
enh.setCallback(new InvocationHandler(){//3.设置回调函数(额外功能代码)
    @Override
    public Object invoke(Object proxy , Method method, Object[] args) throws Throwable{
        System.out.println("start...");
        Object ret = method.invoke(os,args);
        System.out.println("end...");
        return ret;
    }
});
OrderService proxy = (OrderService)enh.create();//4.创建动态代理类
proxy.createOrder();

5.3、AOP(XML版)

<!-- Spring 依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
<!-- AOP 依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

1、创建 UserService 接口

public interface UserService
{
    void addUser();
    void deleteUser();
    void updateUser();
    void queryUser();
}

2、创建 UserServiceImpl 类实现 UserService 接口

public class UserServiceImpl implements UserService 
{
    @Override
    public void addUser() {
        System.out.println("add User...");
    }

    @Override
    public void deleteUser() {
        System.out.println("delete User...");
    }

    @Override
    public void updateUser() {
        System.out.println("update User...");
    }

    @Override
    public void queryUser() {
        System.out.println("query User...");
    }
}

3、创建 LogInfo 切面(日志记录类,重点)

  • 在该类使用到 Spring AOP
public class LogInfo
{
    /* 切入点:execution(* com.ruanjia.service.impl.UserServiceImpl.*(..)) 表示UserServiceImpl下所有方法*/
    @Pointcut("execution(* com.ruanjia.service.impl.UserServiceImpl.*(..))")
    public void result() {}

    /*  前置处理*/
    public void before()
    {
        System.out.println("--------前置处理 记录日志-------");
    }

    /*  后置处理*/
    public void after()
    {
        System.out.println("--------后置处理 记录日志-------");
    }

    /*  后置处理*/
    public void afterReturning()
    {
        System.out.println("--------后置处理返回-------");
    }

    /*
     * 环绕通知(增强通知,将目标方法封装起来),可以返回目标方法结果
     * ProceedingJoinPoint:程切入点,增强方法在此实现对被增强方法的功能增强。通过切入点我们可以得到很多东西
     * */
    public void around(ProceedingJoinPoint point)
    {
        try {
            System.out.println("--------环绕通知 前置处理 记录日志-------");

            //获取执行的方法名
            System.out.println(point.getSignature().getName());

            //执行目标方法
            point.proceed();
            System.out.println("--------环绕通知 后置处理 记录日志-------");
        } catch (Throwable throwable)
        {
            throwable.printStackTrace();
            System.out.println("--------环绕通知 异常处理 记录日志-------");
        }
    }
}

4、spring-config.xml(Spring核心配置文件)中使用AOP

4.1、引入AOP头文件约束

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd"

4.2、本次案例完整Spring-config.xml

<?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
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 定义两个bean -->
    <bean id="userService" class="com.ruanjia.service.impl.UserServiceImpl"/>
    <bean id="logInfo" class="com.ruanjia.aop.LogInfo"/>

    <!-- Aop配置:设置expose-proxy属性为true暴露代理(作用:例如有两个目标方法)。-->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut id="qud" expression="execution(* com.ruanjia.service.impl.UserServiceImpl.*(..))"/>
        <!-- ref:指定切面类 -->
        <aop:aspect ref="logInfo">
            <!--
                aop:before:前置通知(切入方法执行前执行)
                aop:after:前置通知(切入方法执行后执行)
                aop:around:环绕通知(围绕着方法执行)
            -->
            <aop:before method="before" pointcut-ref="qud"/>
            <aop:after method="before" pointcut-ref="qud"/>
            <aop:around method="around" pointcut-ref="qud"/>
            <aop:after-returning method="afterReturning" pointcut-ref="qud"/>
        </aop:aspect>
    </aop:config>

</beans>

5、创建 AopMain 类测试

public class AopMain
{
    public static void main(String[] args) 
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
        
        //注意:动态代理是代理接口实现,所以必须返回被代理对象实现的接口。
        UserService service = context.getBean(UserService.class);
        service.addUser();
    }
}

5.4、AOP(注解版)

<!-- Spring 依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
<!-- AOP 依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

1、SpringConfig(核心配置类)

//Configuration注解表示这是一个配置类:相当于 ApplicationContext.xml配置文件
@Configuration
//扫描指定包下的所有Spring 容器中的 bean
@ComponentScan("com.ruanjia")
/**
 * exposeProxy = true:设置expose-proxy属性为true暴露代理。
 * proxyTargetClass = true:设置为true表示使用静态代理(默认 false)
 */
@EnableAspectJAutoProxy //开启AOP代理模式
public class SpringConfig 
{
}

2、UserService 和 UserServiceImpl

public interface UserService 
{
    void addUser();
    void deleteUser();
    void updateUser();
    void queryUser();
}
//把该类交给Spring容器管理
@Service
public class UserServiceImpl implements UserService 
{
    @Override
    public void addUser() {
        System.out.println("add User...");
    }

    @Override
    public void deleteUser() {
        System.out.println("delete User...");
    }

    @Override
    public void updateUser() {
        System.out.println("update User...");
    }

    @Override
    public void queryUser() {
        System.out.println("query User...");
    }
    
}

3、LogInfo

@Component //由Spring容器管理该对象
@Aspect //表示这是一个切面
public class LogInfo
{
    /* 切入点*/
    @Pointcut("execution(* com.ruanjia.service.impl.UserServiceImpl.*(..))")
    public void result() {}

    /*  前置处理*/
    @Before("result()")
    public void before()
    {
        System.out.println("--------前置处理 记录日志-------");
    }

    /*  后置处理*/
    @After("result()")
    public void after()
    {
        System.out.println("--------后置处理 记录日志-------");
    }

    /*  返回结果处理*/
    @AfterReturning("result()")
    public void afterReturning()
    {
        System.out.println("--------目标方法返回结果执行-------");
    }

    /*  环绕通知(增强通知,将目标方法封装起来)*/
    //@Around("result()")
    public void around(ProceedingJoinPoint point)
    {
        try {
            System.out.println("--------环绕通知 前置处理 记录日志-------");
            
            point.proceed(); //真实执行方法

            System.out.println("--------环绕通知 后置处理 记录日志-------");
        } catch (Throwable throwable)
        {
            throwable.printStackTrace();
            System.out.println("--------环绕通知 异常处理 记录日志-------");
        }
    }

}

4、MainAop测试

public class MainAop 
{
    public static void main(String[] args) 
    {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService service = context.getBean(UserService.class);
        service.addUser();
    }
}

测试结果:

在这里插入图片描述

六、Spring Bean后置处理器

  • Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。
  • 每个bean在创建完成之前 ,都会有一个后处理过程,即再加工,对bean做出相关改变和调整.
  • BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等.
  • 你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。

注意:ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。

6.1、定义后置处理器

1、创建 MyBeanPostProcessor (所有bean的后置处理器)

  • 这个类实现了 BeanPostProcessor,所以它是一个Spring Bean的后置处理器。
  • 只需要把这个类注册到容器中,ApplicationContext 会自动识别,然后通过在容器中创建 bean 时,在适当的时候调用它。
public class MyBeanPostProcessor implements BeanPostProcessor
{

    //bean 初始化前的后处理
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
    {
        System.out.println("postProcessBeforeInitialization : " + beanName);
        return bean;
    }

    //bean 初始化后的后处理
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
    {
        System.out.println("postProcessAfterInitialization : " + beanName);
        return bean;
    }
}

2、创建 MyService(普通bean)

  • 用于测试
public class MyBeanService
{
    private String message;

    public void setMessage(String message)
    {
        this.message  = message;
    }

    public void getMessage()
    {
        System.out.println("Your Message : " + message);
    }

    //bean 生命周期初始化方法(初始化执行)
    public void init(){
        System.out.println("Bean init.");
    }

    //bean 生命周期销毁方法(销毁前执行)
    public void destroy()
    {
        System.out.println("Bean  now.");
    }
}

3、Spring-config.xml 配置

<?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
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="myBeanService" class="com.ruanjia.service.MyBeanService" init-method="init" destroy-method="destroy">
        <property name="message" value="hello r"/>
    </bean>

    <!-- 所有bean的后置处理器(只需要放到Spring容器中,容器会自动识别) -->
    <bean class="com.ruanjia.postproce.MyBeanPostProcessor"/>
    
</beans>

4、MyMain(测试类)

public class MyMain
{
    public static void main(String[] args)
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");

        MyBeanService bean = context.getBean(MyBeanService.class);
        bean.getMessage();
    }
}

运行结果:

在这里插入图片描述

七、Spring 事务管理

7.1、什么是事务?

  • 事务是逻辑上的一组操作,要么都执行,要么都不执行。
  • 要么成功(提交事务),要么失败(事务回滚)。

7.1.1、案例一

  • 例如一个业务方法中包含了多个原子性的操作(数据库操作增删改操作)时,要么成功,要么失败(这就是事务)。
  • 注意事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的innodb引擎。但是,如果把数据库引擎变为 myisam,那么程序也就不再支持事务了!
//下面方法业务,包含了两个原子性操作。所以要么都执行成功,要么都执行失败。
public void saveUser()
{
    userMapper.saveUser(user);
    userRoleMapper.saveUserRoles(user, role);
}

7.2、事物的特性(ACID)

  • 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  • 一致性: 执行事务前后,数据保持一致;
  • 隔离性: 并发访问数据库时,一个用户的事物不被其他事务所干扰也就是说多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
  • 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

7.3、Spring 对事务的支持

7.3.1、 MySQL 怎么保证原子性的?
  • 我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行**回滚**,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。
  • 如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
7.3.2、Spring 支持两种方式的事务管理
1、编程式事务管理
  • 通过 TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使。
2、声明式事务管理
  • 推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。

结束语事务后面在Spring 整合 SpringMVC讲解!!!Spring就讲到这够用了!!!想深入了解Spring自行摸索!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值