# SSM框架-->Spring

SSM框架–>Spring

文章目录

1.1 Spring是什么

Spring官网

简介:

​ Spring 是一款目前主流的 Java EE 轻量级开源框架 ,是 Java 世界最为成功的框架之一。Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期

​ Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。

​ Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。

1.2. Spring 的发展

​ 经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

​ 这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。

项目名称描述
Spring DataSpring 提供的数据访问模块,对 JDBC 和 ORM 提供了很好的支持。通过它,开发人员可以使用一种相对统一的方式,来访问位于不同类型数据库中的数据。
Spring Batch一款专门针对企业级系统中的日常批处理任务的轻量级框架,能够帮助开发人员方便的开发出健壮、高效的批处理应用程序。
Spring Security前身为 Acegi,是 Spring 中较成熟的子模块之一。它是一款可以定制化的身份验证和访问控制框架。
Spring Mobile是对 Spring MVC 的扩展,用来简化移动端 Web 应用的开发。
Spring Boot是 Spring 团队提供的全新框架,它为 Spring 以及第三方库一些开箱即用的配置,可以简化 Spring 应用的搭建及开发过程。
Spring Cloud一款基于 Spring Boot 实现的微服务框架。它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来,并通过 Spring Boot 的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。

狭义的Spring特指Spring Framework,通过我们将它 称为Spring框架。Spring框架是一个分层的,面向切面的Java应用程序的一站轻量级框架。

1.2.1Spring Framework的特点

Spring有以下几个特点:

  • 方便解耦,简化开发:Spring可以将所有对象的创建和依赖关系的维护交给Spring容器管理。
  • 方便集成各种优秀框架:Spring不排斥各种优秀框架
  • 降低JavaEE API的使用难度: Spring对开发中常见的,难用的API(JDBC,等)都提供了封装。
  • 方便程序测试: Spring支持JUnit4 ,可以通过注解翻遍的测试Spring程序
  • AOP变成的支持: Spring提供面向切面编程,可以方便地实现对程序功能的扩展。
  • 声明式事务的支持: 只需简单配置就可以完成对事务的管理,而无需手动编程

1.2.2 Spring的体系结构

在这里插入图片描述

1.2.2.1 Data Access/Integration(数据访问/集成)

数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。

  • JDBC 模块:提供了一个 JBDC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。
  • ORM 模块:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
  • OXM 模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
  • JMS 模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
  • Transactions 事务模块:支持编程和声明式事务管理。
1.2.2.2. Web 模块

Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Portlet 组件,具体介绍如下。

  • Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
  • Servlet 模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
  • WebSocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
  • Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。
1.2.2.3. Core Container(Spring 的核心容器)

Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下。

  • Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入。
  • Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
  • Context 上下文模块:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
  • SpEL 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
1.2.2.4. AOP、Aspects、Instrumentation 和 Messaging

在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:

  • AOP 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
  • Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  • Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
  • messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
1.2.2.5. Test 模块

Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。

1.3 Spring IOC(控制反转)

IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。

Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。

1.3.1 IOC的两种实现

IoC 思想基于 IoC 容器实现的,IoC 容器底层其实就是一个 Bean 工厂。Spring 框架为我们提供了两种不同类型 IoC 容器,它们分别是 BeanFactory 和 ApplicationContext。

实现类描述示例代码
ClassPathXmlApplicationContext加载类路径 ClassPath 下指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
FileSystemXmlApplicationContext加载指定的文件系统路径中指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

1.3.2 Spring 中IOC的开发步骤:

  1. 开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用 标签、在 Java 类上使用 @Component 注解等。
  2. Spring 启动时,IoC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
  3. 当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。

这个过程在职责层面发生了控制权的反转,把原本调用者通过代码实现的对象的创建,反转给 IoC 容器来帮忙实现,因此我们将这个过程称为 Spring 的“控制反转”。

1.3.3 依赖注入 (DI)

注:
  • IOC是利用Spring容器创建对象,而DI是对用IOC创建的对象中的对象进行赋值。

  • 依赖注入本质上是 Spring Bean 属性注入的一种,只不过这个属性是一个对象属性而已。

依赖注入的实现方法:

Spring 主要通过以下 3种方式实现属性注入:

  • ​ 构造器注入

    • @RestController
      @RequestMapping("/constructor")
      public class ConstructorController {
          private final DiService diService;
          private final String result;
       
          public ConstructorController(DiService diService) {
              this.diService = diService;
              this.result = diService.test001("constructor");
          }
       
          @GetMapping("/test001")
          public String test001() {
              return diService.test001(this.result);
          }
      }
      
    • //只需要在 <bean> 标签下的 <constructor-arg> 元素中,再次使用 <bean> 元素对内部 Bean 进行定义
      <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="……" class="……">
              <constructor-arg name="……">
                  <!--内部 Bean-->
                  <bean class="……">
                      <constructor-arg name="……" value="……"></constructor-arg>
                      ……
                  </bean>
              </constructor-arg>
          </bean>
      </beans>
      
  • setter 注入(又称设值注入)

    • @RestController
      @RequestMapping("/setter")
      public class SetterController {
          private DiService diService;
       
          @Autowired
          public void setDiService(DiService diService) {
              this.diService = diService;
          }
       
          @GetMapping("/test001")
          public String test001() {
              return diService.test001("setter");
          }
      }
      
    • //set方法注入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 id="outerBean" class="……">
              <property name="……" >
                  <!-- 定义内部 Bean -->
                  <bean class="……">
                      <property name="……" value="……" ></property>
                      ……
                  </bean>
              </property>
          </bean>
      </beans>
      
  • 注解注入

    • @RestController
      @RequestMapping("/annotation")
      public class AnnotationController {
          @Autowired
          private DiService diService;
       
          @GetMapping("/test001")
          public String test001() {
              return diService.test001("annotation");
          }
      
    • /*
      首先配置文件需要先导入相应的aop和context约束以及配置注解的支持<context:annotation-config />
      还需要导入<context:component-scan base-package="自己的报名"/>扫描指定包下的组件,导入这个配置之后,就已经包含导入<context:annotation-config />这个配置了
      */
      
      
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:aop="http://www.springframework.org/schema/aop"
             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.xsd
                          http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
       
          <context:annotation-config />
          <context:component-scan base-package="xxxxx"/>
      </beans>
      
注解方式注册bean:

在以前的开发中,我们主要使用四种注解注册bean,每种注解可以任意使用,只是语义上有所差异:

  • @Component:可以用于注册所有bean
  • @Repository:主要用于注册dao层的bean
  • @Controller:主要用于注册控制层的bean
  • @Service:主要用于注册服务层的bean
    随着springboot的流行,@Bean注解也逐渐的被我们使用起来。Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
注解方式注入依赖(主要有两种):

@Resource :java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
@Autowired :spring注解,默认是以byType的方式去匹配类型相同的bean,可以结合@Qualifier 注解根据byName方式匹配。

@autowire主要有三个属性值:constructor,byName,byType。
  • constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
  • byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
  • byType:查找所有的set方法,将符合符合参数类型的bean注入。
注入集合(数组、List、Map、Set)类型属性==》(开发常见 以set注入为主)

类:

    //1、数组类型属性
    private String[] courses;

    //2、list集合类型属性
    private List<String> list;

    //3、map集合类型属性
    private Map<String,String> maps;

    //4、set集合类型属性
    private Set<String> sets;

    //学生所学的多门课程
    private List<Course> courseList;

    public void setCourseList(List<Course> courseList) {
        this.courseList = courseList;
    }

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }

	public void test(){
        System.out.println(Arrays.toString(courses));
        System.out.println(list);
        System.out.println(maps);
        System.out.println(sets);
        System.out.println(courseList);
    }

Bean.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.xsd">

    <!--集合类型属性注入-->
    <bean id="stu" class="xxxx">
        <!--数组类型属性注入-->
        <property name="courses">
            <array>
                <value>Java</value>
                <value>C++</value>
                <value>Python</value>
            </array>
        </property>

        <!--list类型属性注入-->
        <property name="list">
            <list>
                <value>小明</value>
                <value>小红</value>
            </list>
        </property>

        <!--map类型属性注入-->
        <property name="maps">
            <map>
                <entry key="Java" value="java"></entry>
                <entry key="C++" value="c++"></entry>
            </map>
        </property>

        <!--set类型属性注入-->
        <property name="sets">
            <set>
                <value>北京</value>
                <value>上海</value>
            </set>
        </property>

        <!--注入list集合类型,值是对象-->
        <property name="courseList">
            <list>
                <ref bean="course1"></ref>
                <ref bean="course2"></ref>
            </list>
        </property>
    </bean>

    <!--创建多个course对象-->
    <bean id="course1" class="xxxxxxx">
        <property name="cname" value="Spring5框架"></property>
    </bean>

    <bean id="course2" class="xxxxxxxx">
        <property name="cname" value="MyBatis框架"></property>
    </bean>
</beans>

Text类

    @Test
    public void testCollection1(){

        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Stu stu = context.getBean("stu",xxx.class);
        stu.test();
    }

输出结果

[Java, C++, Python]
[小明, 小红]
{Java=java, C++=c++}
[北京, 上海]
[Course{cname='Spring5框架'}, Course{cname='MyBatis框架'}]

Process finished with exit code 0

1.4 Spring Bean定义

由 Spring IoC 容器管理的对象称为 Bean。

Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。

  • Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
  • XML 配置文件采用树形结构,结构清晰,相较于 Properties 文件更加灵活。但是 XML 配置比较繁琐,适用于大型的复杂的项目。

1.4.1Bean.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="helloWorld" class="com.hh.HelloWorld">
        <property name="message" value="Hello World!"/>
    </bean>
</beans>

在 XML 配置的 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。

属性名称描述
idBean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。
name该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。
class该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
scope表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。
constructor-arg 元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。
property元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。
ref 和 等元素的子元索,用于指定对某个 Bean 实例的引用,即 元素中的 id 或 name 属性。
value 和 等元素的子元素,用于直接指定一个常量值。
list用于封装 List 或数组类型的属性注入。
set用于封装 Set 类型的属性注入。
map用于封装 Map 类型的属性注入。
entry 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值。
init-method容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
destroy-method容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
lazy-init懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效

1.4.2 短命名空间注入

我们在通过构造函数或 setter 方法进行属性注入时,通常是在 元素中嵌套 和 元素来实现的。这种方式虽然结构清晰,但书写较繁琐。

Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。

  • p 命名空间 元素中嵌套的 property 元素 是 setter 方式属性注入的一种快捷实现方式
  • 命名空间 元素中嵌套的 constructor 元素 是构造函数属性注入的一种快捷实现方式

1.4,3p 命名空间注入

p 命名空间是 setter 方式属性注入的一种快捷实现方式。通过它,我们能够以 bean 属性的形式实现 setter 方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。

首先我们需要在配置文件的 元素中导入以下 XML 约束。

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

在导入 XML 约束后,我们就能通过以下形式实现属性注入。

<bean id="Bean 唯一标志符" class="包名+类名" p:普通属性="普通属性值" p:对象属性-ref="对象的引用">

使用 p 命名空间注入依赖时,必须注意以下 3 点:

  • Java 类中必须有 setter 方法;
  • Java 类中必须有无参构造器(类中不包含任何带参构造函数的情况,无参构造函数默认存在);
  • 在使用 p 命名空间实现属性注入前,XML 配置的 元素内必须先导入 p 命名空间的 XML 约束。

1.4.4 Bean的作用域

默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。

我们可以在 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

Spring 5 共提供了 6 种 scope 作用域,如下表。

作用范围描述
singleton默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例
prototype原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。
request每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
session同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
application同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
websocketwebsocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。
例:–>类:(singleton)单例模式
public class Bean {
    private String str;
    public void setStr(String str) {
        this.str = str;
    }
}

Bean.xml

<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">
   
	<!--单例模式 singleton-->
    <bean id="Bean" class="xxxxxxx" scope="singleton">
        <property name="str" value="C语言"></property>
    </bean>

</beans>

测试:

    public static void main(String[] args) {
        //获取 ApplicationContext 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        
        //创建两个实例,如果输出相同,则表示为一个对象
        Bean Bean = context.getBean("Bean", Bean.class);
        Bean Bean2 = context.getBean("Bean", Bean.class);
        System.out.println(Bean);
        System.out.println(Bean2);
    }

运行类中的 main() 方法,控制台输出如下。

net.biancheng.c.SingletonBean@65e2dbf3
net.biancheng.c.SingletonBean@65e2dbf3

从控制台的输出可以看出,两次获得的 Bean 实例的地址完全一样,这说明 IoC 容器只创建了一个 singletonBean 实例。由于 singleton 是 Spring IoC 容器的默认作用域,因此即使省略 scope 属性,控制台的输出结果也一样的。

例:–>类:(prototype)原型模式

Bean.xml:

<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">
    <!--单例模式 singleton-->
    <bean id="Bean" class="xxxxx" scope="singleton">
        <property name="str" value="C语言中文网"></property>
    </bean>
    <!--原型模式 prototype-->
    <bean id="Bean" class="xxxxxx" scope="prototype">
        <property name="str" value="c.biancheng.net"></property>
    </bean>
</beans>

执行main 函数,控制台输出如下。

net.biancheng.c.PrototypeBean@61f8bee4
net.biancheng.c.PrototypeBean@7b49cea0

从运行结果可以看出,两次输出的内容并不相同,这说明在 prototype 作用域下,Spring 容器创建了两个不同的 prototypeBean 实例。

1.5 Spring Bean生命周期

Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:

  1. Bean 的实例化
  2. Bean 属性赋值
  3. Bean 的初始化
  4. Bean 的使用
  5. Bean 的销毁

在这里插入图片描述

1.5.1Bean 生命周期的整个执行过程描述如下。

  1. Spring 启动,查找并加载需要被 Spring 管理的 Bean,对 Bean 进行实例化。
  2. 对 Bean 进行属性注入。
  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  6. 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
  10. 如果在 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

1.5.2 自定义 Bean 的生命周期

我们可以在 Spring Bean 生命周期的某个特定时刻,指定一些生命周期回调方法完成一些自定义的操作,对 Bean 的生命周期进行管理。

Bean 的生命周期回调方法主要有两种:

  • 初始化回调方法:在 Spring Bean 被初始化后调用,执行一些自定义的回调操作。
  • 销毁回调方法:在 Spring Bean 被销毁前调用,执行一些自定义的回调操作。

我们可以通过以下 3 种方式自定义 Bean 的生命周期回调方法

  • 通过接口实现

    • 我们可以在 Spring Bean 的 Java 类中,通过实现 InitializingBean 和 DisposableBean 接口,指定 Bean 的生命周期回调方法。

      回调方式接口方法说明
      初始化回调InitializingBeanafterPropertiesSet()指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。
      销毁回调DisposableBeandestroy()指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。

      例:类:

      public class XiaoHu implements InitializingBean, DisposableBean {
      
          public String name;
          public int age;
      
          public void setName(String name) {
              this.name = name;
          }
      
          public void setAge(int age) {
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "XiaoHu{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      '}';
          }
          
          /**
           * 调用销毁
           *
           * @throws Exception
           */
          @Override
          public void destroy() throws Exception {
              System.out.println("调用destroy()方法");
          }
          
          /**
           * 调用初始化回调函数
           *
           * @throws Exception
           */
          @Override
          public void afterPropertiesSet() throws Exception {
              System.out.println("调用afterPropertiesSet()方法");
          }
      }
      
      

      Bean.xml:

      <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="xiaohu" class="com.text.XiaoHu">
              <property name="name" value="小胡"></property>
              <property name="age" value="22"></property>
          </bean>
      </beans>
      

      测试:

      public class MainApp {
      
          public static void main(String[] args) {
              //获取 ClassPathXmlApplicationContext 容器
              ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
              XiaoHu xiaohu = context.getBean("xiaohu", XiaoHu.class);
      		System.out.println(xiaohu);
      		
              //手动销毁 Bean
              context.close();
          }
      }
      

      结果:

      调用afterPropertiesSet()方法
      XiaoHu{name='小胡', age=22}
      调用destroy()方法
      
      进程已结束,退出代码为 0
      
  • 通过 XML 配置实现

    • 我们还可以在 Spring 的 XML 配置中,通过 元素中的 init-method 和 destory-method 属性,指定 Bean 的生命周期回调方法。

      XML 配置属性描述
      init-method指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。
      destory-method指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。

      例:类:

      public class XiaoHu {
      
          public String name;
          public int age;
      
      
          public void setName(String name) {
              this.name = name;
          }
      
          public void setAge(int age) {
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "XiaoHu{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      '}';
          }
      
          
          public void destroy() throws Exception {
              System.out.println("调用destroy()方法");
          }
      
          
          public void init() throws Exception {
              System.out.println("调用afterPropertiesSet()方法");
          }
      }
      

      Bean.xml

      <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="xiaohu" class="com.text.XiaoHu" init-method="init" destroy-method="destroy">
              <property name="name" value="小胡"></property>
              <property name="age" value="22"></property>
          </bean>
      </beans>
      

      测试/结果:同上

  • 使用注解实现

    • 我们还可以通过 JSR-250 的 @PostConstruct 和 @PreDestroy 注解,指定 Bean 的生命周期回调方法。

      注解描述
      @PostConstruct指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。
      @PreDestroy指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。

      例:类:

      public class XiaoHu {
      
          public String name;
          public int age;
      
      
          public void setName(String name) {
              this.name = name;
          }
      
          public void setAge(int age) {
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "XiaoHu{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      '}';
          }
      
          @PreDestroy
          public void destroy() throws Exception {
              System.out.println("调用destroy()方法");
          }
      
          @PostConstruct
          public void init() throws Exception {
              System.out.println("调用afterPropertiesSet()方法");
          }
      }
      
      

      Bean.xml

      <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">
      
          <!--注解扫描-->
          <context:component-scan base-package="com.text"></context:component-scan>
      
          <bean id="xiaohu" class="com.text.XiaoHu">
              <property name="name" value="小胡"></property>
              <property name="age" value="22"></property>
          </bean>
      </beans>
      
      注:使用注解开发需要配置注解扫描(<context:component-scan base-package=“包所在的路径”>)

如果一个 Bean 中有多种生命周期回调方法时,优先级顺序为:注解 > 接口 > XML 配置。

1.5.3 Spring中Bean的装配方式

​ Bean的装配方式可以理解为依赖关系的注入,Bean的装配方式即Bean依赖注入的方式。Spring容器支持多种形式的Bean的装配,如基于XML的装配,基于注解(Annotation),自动装配

1.基于XML的装配

​ 在Spring实例化Bean的过程中,Spring会首先调用Bean的默认构造方法来实例化Bean对象,然后通过反射的方式调用setter方法注入属性值。因此,设值注入必须满足以下两点:

  • Bean类必须提供一个默认的构造方法。
  • Bean类必须为需要注入的属性提供相应的setter方法。

使用设值注入时,在Spring配置文件中,需要使用元素的子元素<property>来为每个属性注入值;

再使用有参构造时,需要使用元素的子元素<constructor-arg>来为每个属性注入值

例:类:

public class XiaoHu {

    public String name;
    public int age;

    /**
     * 使用构造注入
     * 提供相应的有参构造方法
     * @param name
     * @param age
     */
    public XiaoHu(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 使用设值注入
     * 提供相应的空参构造方法
     * 为所有属性设置相应的setter方法
     */
    public XiaoHu() { }

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

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {return "XiaoHu{" + "name='" + name + '\'' + ", age=" + age +
                '}';
    }
}

Bean.xml

<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 id="xiaohu1" class="com.text.XiaoHu">
        <constructor-arg index="0" value="小胡"/>
        <constructor-arg index="1" value="22"/>
    </bean>

    <bean id="xiaohu2" class="com.text.XiaoHu">
        <property name="name" value="小胡"></property>
        <property name="age" value="22"></property>
    </bean>
</beans>

测试:

public class MainApp {

    public static void main(String[] args) {
        //获取 ClassPathXmlApplicationContext 容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        XiaoHu xiaohu1 = context.getBean("xiaohu1", XiaoHu.class);
        XiaoHu xiaohu2 = context.getBean("xiaohu2", XiaoHu.class);
        System.out.println("使用有参构造"+xiaohu1);
        System.out.println("使用无参构造"+xiaohu2);

    }
}

结果:

使用有参构造XiaoHu{name='小胡', age=22}
使用无参构造XiaoHu{name='小胡', age=22}

进程已结束,退出代码为 0
2.基于Annotation的装配(注解)

Spring 通过注解实现自动装配的步骤如下:

  1. 引入依赖 使用注解的第一步,就是要在项目中引入以下 Jar 包。

      • org.springframework.core-5.3.13.jar
      • org.springframework.beans-5.3.13.jar
      • spring-context-5.3.13.jar
      • spring-expression-5.3.13.jar
      • commons.logging-1.2.jar
      • spring-aop-5.3.13.jar
  2. 开启组件扫描

    ​ 因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

  3. 使用注解定义 Bean

    ​ Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

    注解说明
    @Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
    @Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
    @Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
    @Controller该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  4. 依赖注入

    ​ 我们可以通过以下注解将定义好 Bean 装配到其它的 Bean 中。

    注解说明
    @Autowired可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。 @Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用
    @Resource作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 @Resource 中有两个重要属性:name 和 type。 Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配;如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
    @Qualifier与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

    例:类

    1.创建Dao层,在其下创建UserDao类的接口

    public interface UserDao {
        public void save();
    }
    

    2.在Dao层下创建UserDaoimpl类实现Use人Dao接口

    @Repository("userDao")
    public class UserDaoimpl implements UserDao {
        @Override
        public void save() {
            System.out.println("userdao......save....");
        }
    }
    

    3.创建Service层,在其下创建UserServiceimpl类的接口

    public interface UserService {
        public void save();
    }
    

    4.在Service层下创建UserServiceimpl来实现User Service的接口

    @Service("userService")
    public class UserServiceimpl implements UserService {
        @Resource(name="userDao")
         private UserDao userDao;
    
        @Override
        public void save() {
            this.userDao.save();
            System.out.println("userService...save.....");
        }
    }
    

    5.创建Controller层,

    @Controller("userController")
    public class UserController {
        @Resource(name="userService")
        private UserService userService;
        public void save(){
            this.userService.save();
            System.out.println("userController.....save....");
        }
    }
    

    6.创建Bean.xml

    <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">
    
        <!--注解扫描-->
        <context:component-scan base-package="com"></context:component-scan>
    
        <bean id="userDao" class="com.Dao.impl.UserDaoimpl"></bean>
        <bean id="userService" class="com.Srevice.impl.UserServiceimpl"></bean>
        <bean id="userController" class="com.Controller.UserController"></bean>
    </beans>
    

    7.创建测试类

    public class MainApp {
    
        public static void main(String[] args) {
            //获取 ClassPathXmlApplicationContext 容器
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            UserController userController=context.getBean("userController",UserController.class);
           userController.save();
        }
    }
    

    8.测试结果

    userdao......save....
    userService...save.....
    userController.....save....
    
    进程已结束,退出代码为 0
    
    
3.自动装配

Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表。

属性值说明
byName按名称自动装配。 Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。
byType按类型自动装配。 Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。
constructor与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。 其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。
default表示默认采用上一级元素 设置的自动装配规则(default-autowire)进行装配。
no默认值,表示不使用自动装配,Bean 的依赖关系必须通过 和 元素的 ref 属性来定义。

例:

1,2,3同上–>注解装配的步骤

4.更改Service层下的UserServiceimpl类为:(加入setter方法)

@Service("userService")
public class UserServiceimpl implements UserService {
    //@Resource(name="userDao")
     private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        this.userDao.save();
        System.out.println("userService...save.....");
    }
}

5.更改Controller层下UserControllerller为:(加入setter方法)

@Controller("userController")
public class UserController {
    //@Resource(name="userService")
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void save(){
        this.userService.save();
        System.out.println("userController.....save....");
    }
}

6.更改Bean.xml

<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">

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

    <bean id="userDao" class="com.Dao.impl.UserDaoimpl"></bean>
    <bean id="userService" class="com.Srevice.impl.UserServiceimpl" autowire="byName"></bean>
    <bean id="userController" class="com.Controller.UserController" autowire="byName"></bean>
</beans>

1.6 Spring AOP

简介:

​ 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

在这里插入图片描述

主要功能:

​ 日志记录,性能统计,安全控制,事务处理,异常处理等等。

主要意图:

​ 将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

1.6.1 AOP/OOP 区分

  • AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
  • 而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
  • 上面的陈述可能过于理论化,举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。
  • 同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。
  • 换而言之,OOD/OOP面向名词领域,AOP面向动词领域。

​ 很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 的接口也一样能够做到,我想这个观点是值得商榷的。AOP和定义良好的 OOP 的接口可以说都是用来解决并且实现需求中的横切问题的方法。但是对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法避免的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会这样,你只需要修改相应的 Aspect,再重新编织(weave)即可。 当然,AOP 也绝对不会代替 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,以此之长,补彼之短。

1.6.2 AOP 术语

与大多数的技术一样,AOP 已经形成一套属于自己的概念和术语。

在这里插入图片描述

名称说明
Joinpoint(连接点)AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。 在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。
Pointcut(切入点)又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知)指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。
Target(目标)指代理的目标对象,通常也被称为被通知(advised)对象。
Weaving(织入)指把增强代码应用到目标对象上,生成代理对象的过程。
Proxy(代理)指生成的代理对象。
Aspect(切面)切面是切入点(Pointcut)和通知(Advice)的结合。

Advice 直译为通知,也有人将其翻译为“增强处理”,共有 5 种类型,如下表所示。

通知说明
before(前置通知)通知方法在目标方法调用之前执行
after(后置通知)通知方法在目标方法返回或异常后调用
after-returning(返回后通知)通知方法会在目标方法返回后调用
after-throwing(抛出异常通知)通知方法会在目标方法抛出异常后调用
around(环绕通知)通知方法会将目标方法封装起来

1.6.3 AOP 的优势

AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。

在 Spring 框架中使用 AOP 主要有以下优势。

  • 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品,最重要的是,这种服务是声明式事务管理。
  • 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
  • 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。

1.6.4 AOP 可分为两类:CGLIB和AspectJ

目前最流行的 AOP 实现(框架)主要有两个,分别为 Spring AOPAspectJ

AOP 框架说明
Spring AOP是一款基于 AOP 编程的框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。 Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。Spring AOP 支持 2 种代理方式,分别是基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。
AspectJ是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。 AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。

在这里插入图片描述

动态代理

AOP中的代理说是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用。Spring 中的AOP有两种,JDK动态代理,CGLIB代理

1 JDK动态代理

JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。

例:

1.创建UserDao接口:

public interface UserDao {
    public void addUser();
    public void deleteUser();
}

2.创建UserDao接口的实现类:

@Repository("userDao")
public class UserDaoimpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

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

3.创建切面类:

public class MyAspect {
    public void Check_Permissions(){
        System.out.println("模拟检查权限。。。。");
    }
    public void log(){
        System.out.println("模拟记录日志。。。");
    }
}

4.创建代理类:–>实现InvocationHandler接口,并编写代理方法

public class JdkProxy implements InvocationHandler {
    //生命目标类接口
    private UserDao userDao;
    //创建代理方法
    public Object createProxy(UserDao userDao){
        this.userDao=userDao;
        //类加载器
        ClassLoader classLoader = JdkProxy.class.getClassLoader();
        //被代理类实现的所有接口
        Class<?>[] clazz = userDao.getClass().getInterfaces();
        return Proxy.newProxyInstance(classLoader,clazz,this);
    }
    
    /**
     * 所有的动态代理都会调用invoke()方法
     * @param proxy 被代理后的对象
     * @param method  见还要被哦执行的方法(反射)
     * @param args 执行方法是需要的参数
     * @return
     * @throws Throwable
     */

  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//生命切面
        MyAspect myAspect = new MyAspect();
        //前增强
        myAspect.Check_Permissions();
        //在目标类上调用方法
        Object obj = method.invoke(userDao, args);
        //后增强
        myAspect.log();
        return obj;
    }
}

5.创建测试类方法

public class JdkTest {
    public static void main(String[] args) {
        //创建代理对象
        JdkProxy jdkProxy = new JdkProxy();
        //创建目标对象
        UserDaoimpl userDao = new UserDaoimpl();
        UserDao userDao1 = (UserDao)jdkProxy.createProxy(userDao);
        //执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }
}

6.测试结果:

模拟检查权限。。。。
添加用户
模拟记录日志。。。
模拟检查权限。。。。
删除用户
模拟记录日志。。。

进程已结束,退出代码为 0

2.CGLIB代理

通过前面的学习可知,JDK的动态代理用起来非常简单,但它是有局限性的,使用动态代理的对象必须实现一个或多个接口(如UserDaoImpl实现了UserDao接口)。 如果想代理没有实现接口的类,那么可以使用CGLIB代理.
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。
例:

1,2同上

3.创建代理类CglibProxy,–>该类需要实现MethodInterceptor接口,并在接口中实现interceptor方法

import com.Aspect.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

//代理类
public class CglibProxy implements MethodInterceptor {
    //代理方法
    public Object createProxy(Object target){
        //创建一个动态类对象
        Enhancer enhancer=new Enhancer();
        //确定要增强的类,设置其父类
        enhancer.setSuperclass(target.getClass());
        //添加回调函数
        enhancer.setCallback(this);
        //返回创建的代理类
        return enhancer.create();
    }
    /**
     * proxy CGlib根据父类生成的代理对象
     * method 拦截的方法
     * args 拦截方法的参数组
     * methodProxy 方法的代理对象,用于执行父类的方法
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //创建切面类对象
        MyAspect myAspect=new MyAspect();
        //前增强
        myAspect.Check_Permissions();
        //目标方法执行
        Object obj=methodProxy.invokeSuper(proxy,args);
        //后增强
        myAspect.log();
        return obj;
    }
}

4.创建测试类CglibTest

public class CglibTest {
    public static void main(String[] args){
        //创建代理类对象
        CglibProxy cglibProxy=new CglibProxy();
        //创建目标对象
        UserDao userDao=new UserDao();
        //获取增强后的目标对象
        UserDao userDao1=(UserDao) cglibProxy.createProxy(userDao);
        //执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }
}	

5,测试结果

模拟检查权限。。。。
添加用户
模拟记录日志。。。
模拟检查权限。。。。
删除用户
模拟记录日志。。。

进程已结束,退出代码为 0

1.6.5 基于代理类的AOP实现

Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)。

1.6.5.1 Spring 的通知类型

pring AOP 按照通知(Advice)织入到目标类方法的连接点位置,为 Advice 接口提供了 6 个子接口,如下表。

通知类型接口描述
前置通知org.springframework.aop.MethodBeforeAdvice在目标方法执行前实施增强。
后置通知org.springframework.aop.AfterReturningAdvice在目标方法执行后实施增强。
后置返回通知org.springframework.aop.AfterReturningAdvice在目标方法执行完成,并返回一个返回值后实施增强。
环绕通知org.aopalliance.intercept.MethodInterceptor在目标方法执行前后实施增强。
异常通知org.springframework.aop.ThrowsAdvice在方法抛出异常后实施增强。
引入通知org.springframework.aop.IntroductionInterceptor在目标类中添加一些新的方法和属性。
1.6.5.2 ProxyFactoryBean 的常用属性如下表所示。
属性描述
target需要代理的目标对象(Bean)
proxyInterfaces代理需要实现的接口,如果需要实现多个接口,可以通过 元素进行赋值。
proxyTargetClass针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理
interceptorNames拦截器的名字,该属性的取值既可以是拦截器、也可以是 Advice(通知)类型的 Bean,还可以是切面(Advisor)的 Bean。
singleton返回的代理对象是否为单例模式,默认值为 true。
optimize是否对创建的代理进行优化(只适用于CGLIB)。
1.6.5.3 一般切面的 AOP 开发

当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。

例:

下面我们就通过一个简单的实例演示下一般切面的 AOP 开发流程。

  1. 创建 Java 工程,并将以下依赖引入到工程中。
  • org.springframework.core-5.3.13.jar
  • org.springframework.beans-5.3.13.jar
  • spring-context-5.3.13.jar
  • spring-expression-5.3.13.jar
  • commons.logging-1.2.jar
  • spring-aop-5.3.13.jar

2.创建一个UserDao的接口

public interface UserDao {
    public void add();
    public void delete();
    public void modify();
    public void get();
}

3.创建 UserDao 的实现类 UserDaoImpl,

public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("正在执行 UserDao 的 add() 方法……");
    }
    @Override
    public void delete() {
        System.out.println("正在执行 UserDao 的 delete() 方法……");
    }
    @Override
    public void modify() {
        System.out.println("正在执行 UserDao 的 modify() 方法……");
    }
    @Override
    public void get() {
        System.out.println("正在执行 UserDao 的 get() 方法……");
    }
}

4.UserDaoBeforeAdvice 的前置增强类—>实现MethodBeforeAdvice接口,并实现invoke方法

public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("正在执行前置增强操作…………");
    }
}

5.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"
       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
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--******Advisor : 代表一般切面,Advice 本身就是一个切面,对目标类所有方法进行拦截(* 不带有切点的切面.针对所有方法进行拦截)*******-->
    <!-- 定义目标(target)对象 -->
    <bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl"></bean>
    <!-- 定义增强 -->
    <bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice"></bean>
    <!--通过配置生成代理 UserDao 的代理对象 -->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 设置目标对象 -->
        <property name="target" ref="userDao"/>
        <!-- 设置实现的接口 ,value 中写接口的全路径 -->
        <property name="proxyInterfaces" value="net.biancheng.c.dao.UserDao"/>
        <!-- 需要使用value:增强 Bean 的名称 -->
        <property name="interceptorNames" value="beforeAdvice"/>
    </bean>
</beans>

6.创建一个名为 MainApp 的类

public class MainApp {
    public static void main(String[] args) {
        //获取 ApplicationContext 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        //获取代理对象
        UserDao userDao = context.getBean("userDaoProxy", UserDao.class);
        //调用 UserDao 中的各个方法
        userDao.add();
        userDao.delete();
        userDao.get();
        userDao.modify();
    }
}

7.测试结果

正在执行前置增强操作…………
正在执行 UserDao 的 add() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 delete() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 get() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 modify() 方法……

1.6.6 Spring集成AspectJ(静态代理)

Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架来实现,其中使用最多的就是 AspectJ。

AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,它并不是 Spring 组成部分,是一款独立的 AOP 框架。

使用 AspectJ 需要在 Spring 项目中导入 Spring AOP 和 AspectJ 相关 Jar 包。

  • spring-aop-xxx.jar
  • spring-aspects-xxx.jar
  • aspectjweaver-xxxx.jar
1.6.6.1 Spring使用AspectJ进行AOP开发(基于XML)

Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 aop:config 元素。

  • 在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 aop:config 元素中;
  • 在 Spring 配置中,可以使用多个 aop:config。
  • 每一个 aop:config 元素内可以包含 3 个子元素: pointcut、advisor 和 aspect(切面) ,这些子元素必须按照这个顺序进行声明。
1.配置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
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
    
</beans>
2.配置XML文件—>配置切面

在 Spring 配置文件中,使用 aop:aspect 元素定义切面。该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 aop:aspect 之前需要先定义一个普通的 Spring Bean。

<aop:config>    
	<aop:aspect id="myAspect" ref="aBean">       
   		 ...    
    </aop:aspect>
</aop:config>

其中,id 用来定义该切面的唯一标识名称,ref 用于引用普通的 Spring Bean。

3.配置XML文件—>配置切点

aop:pointcut 用来定义一个切入点,用来表示对哪个类中的那个方法进行增强。它既可以在 aop:pointcut 元素中使用,也可以在 aop:aspect 元素下使用。

  • 当 aop:pointcut元素作为 aop:config 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;
  • 当 aop:pointcut 元素作为 aop:aspect 元素的子元素时,表示该切入点只对当前切面有效。
<aop:config>    
	<aop:pointcut id="myPointCut"  expression="execution(* net.biancheng.service.*.*(..))"/>
</aop:config>

其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。

execution 的语法格式格式为:

execution([权限修饰符] [返回值类型] [类的完全限定名] 方法名称

其中:

  • 返回值类型、方法名、参数列表是必须配置的选项,而其它参数则为可选配置项。
  • 返回值类型:*表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。
  • 类的完全限定名:指定包名 + 类名。
  • 方法名:*代表所有方法,set* 代表以 set 开头的所有方法。
  • 参数列表:(..)代表所有参数;(*)代表只有一个参数,参数类型为任意类型;(*,String)代表有两个参数,第一个参数可以为任何值,第二个为 String 类型的值。

举例 1:对 net.biancheng.c 包下 UserDao 类中的 add() 方法进行增强,配置如下。

execution(* net.biancheng.c.UserDao.add(..))

举例 2:对 net.biancheng.c 包下 UserDao 类中的所有方法进行增强,配置如下。

execution(* net.biancheng.c.UserDao.*(..))

举例 3:对 net.biancheng.c 包下所有类中的所有方法进行增强,配置如下。

execution(* net.biancheng.c.*.*(..))
4.配置XML文件—>配置通知

AspectJ 支持 5 种类型的 advice,如下。

<aop:aspect id="myAspect" ref="aBean">    
	<!-- 前置通知 -->    
	<aop:before pointcut-ref="myPointCut" method="..."/>       
	<!-- 后置通知 -->    
	<aop:after-returning pointcut-ref="myPointCut" method="..."/>    
	<!-- 环绕通知 -->    
	<aop:around pointcut-ref="myPointCut" method="..."/>    
	<!-- 异常通知 -->    
	<aop:after-throwing pointcut-ref="myPointCut" method="..."/>    
	<!-- 最终通知 -->    
	<aop:after pointcut-ref="myPointCut" method="..."/>    
	.... 
</aop:aspect>
示例

1.新建一个Java 项目,并将以下依赖 Jar 包导入到该项目中。

  • commons-logging-1.2.jar
  • spring-aop-5.3.13.jar
  • spring-aspects-5.3.13.jar
  • spring-beans-5.3.13.jar
  • spring-context-5.3.13.jar
  • spring-core-5.3.13.jar
  • spring-expression-5.3.13.jar
  • aspectjweaver-1.9.7.jar

2.创建一个名为 OrderDao 的接口,代码如下。

public interface OrderDao {
    public void add();
    public void delete();
    public Integer modify();
    public void get();
}

3.创建 OrderDao 的实现类 OrderDaoImpl

public class OrderDaoImpl implements OrderDao {
    @Override
    public void add() {
        System.out.println("正在执行 OrderDao 中的 add() 方法");
    }
    @Override
    public void delete() {
        System.out.println("正在执行 OrderDao 中的 delete() 方法");
    }
    @Override
    public int modify() {
        System.out.println("正在执行 OrderDao 中的 modify() 方法");
        return 1;
    }
    @Override
    public void get() {
        //异常
        int a = 10 / 0;
        System.out.println("正在执行 OrderDao 中的 get() 方法");
    }
}

4.创建一个名为 MyOrderAspect 的切面类

public class MyOrderAspect {
   
    public void before() {
        System.out.println("前置增强……");
    }
    public void after() {
        System.out.println("最终增强……");
    }
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕增强---前……");
        proceedingJoinPoint.proceed();
        System.out.println("环绕增强---后……");
    }
    public void afterThrow(Throwable exception) {
        System.out.println("异常增强…… 异常信息为:" + exception.getMessage());
    }
    public void afterReturning(Object returnValue) {
        System.out.println("后置返回增强…… 方法返回值为:" + returnValue);
    }
}

5.配置文件 Beans2.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--定义 Bean-->
    <bean id="orderDao" class="net.biancheng.c.dao.impl.OrderDaoImpl"></bean>
    <!--定义切面-->
    <bean id="myOrderAspect" class="net.biancheng.c.MyOrderAspect"></bean>
    <aop:config>
        <aop:pointcut id="beforePointCut" expression="execution(* net.biancheng.c.dao.OrderDao.add(..))"/>
        <aop:pointcut id="throwPointCut" expression="execution(* net.biancheng.c.dao.OrderDao.get(..))"/>
        <aop:pointcut id="afterReturnPointCut" expression="execution(* net.biancheng.c.dao.OrderDao.modify(..))"/>
        <aop:pointcut id="afterPointCut" expression="execution(* net.biancheng.c.dao.OrderDao.*(..))"/>
        <aop:aspect ref="myOrderAspect">
            <!--前置增强-->
            <aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
            <!--后置返回增强-->
            <aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
                                 returning="returnValue"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="afterThrow" pointcut-ref="throwPointCut"
                                throwing="exception"></aop:after-throwing>
            <!--最终通知-->
            <aop:after method="after" pointcut-ref="afterPointCut"></aop:after>
            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="beforePointCut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

6.创建一个名 MainApp 的测试类

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans2.xml");
        OrderDao orderDao = context2.getBean("orderDao", OrderDao.class);
        orderDao.add();
        orderDao.delete();
        orderDao.modify();
        orderDao.get();
    }
}

7.测试结果

前置增强……
环绕增强---前……
正在执行 OrderDao 中的 add() 方法
环绕增强---后……
最终增强……
正在执行 OrderDao 中的 delete() 方法
最终增强……
正在执行 OrderDao 中的 modify() 方法
最终增强……
后置返回增强…… 方法返回值为:1
最终增强……
异常增强…… 异常信息为:/ by zero

其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。

1.6.6.2 Spring使用AspectJ进行AOP开发(基于注解)

在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。

为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。

名称说明
@Aspect用于定义一个切面。
@Pointcut用于定义一个切入点。
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于 ThrowAdvice。
@After用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。
1)启用 @AspectJ 注解支持—>使用 Java 配置类启用

我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。

@Configuration
@ComponentScan(basePackages = "net.biancheng.c") //注解扫描
@EnableAspectJAutoProxy //开启 AspectJ 的自动代理
public class AppConfig {
}
2)启用 @AspectJ 注解支持—>基于 XML 配置启用

在 Spring 的 XML 配置文件中,添加以下内容启用 @AspectJ 注解支持。

<!-- 开启注解扫描 -->
<context:component-scan base-package="net.biancheng.c"></context:component-scan>
<!--开启AspectJ 自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
定义切面 @Aspect

我们可以通过 @Aspect 注解将一个 Bean 定义为切面。

在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。

我们可以在 XML 配置中通过一些配置将这个类定义为一个 Bean,如下。

<bean id = "myAspect" class = "net.biancheng.c.MyAspect">   ...</bean>

在定义完 Bean 后,我们只需要在Bean 对应的 Java 类中使用一个 @Aspect 注解,将这个 Bean 定义为一个切面,代码如下。

@Aspect //定义为切面
public class MyAspect {
}
定义切点 @Pointcut

在 AspectJ 中,我们可以使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void,示例代码如下。

// 要求:方法必须是private,返回值类型为 void,名称自定义,没有参数
@Pointcut("execution(*net.biancheng..*.*(..))")private void myPointCut() {}

@Pointcut 注解中有一个 value 属性,这个属性的值就是切入点表达式。

定义通知

AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。

注解说明
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于 ThrowAdvice。
@After用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。
示例:

1.新建一个Java 项目,并将以下依赖 Jar 包导入到该项目中。

  • commons-logging-1.2.jar
  • spring-aop-5.3.13.jar
  • spring-aspects-5.3.13.jar
  • spring-beans-5.3.13.jar
  • spring-context-5.3.13.jar
  • spring-core-5.3.13.jar
  • spring-expression-5.3.13.jar
  • aspectjweaver-1.9.7.jar

2.创建一个UserDao接口

public interface UserDao {
    public void add();
    public void delete();
    public int modify();
    public void get();
}

3.创建一个UserDaoImpl类实现UserDao接口

@Component("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("正在执行 UserDao 的 add 方法");
    }
    @Override
    public void delete() {
        System.out.println("正在执行 UserDao 的 delete 方法");
    }
    @Override
    public int modify() {
        System.out.println("正在执行 UserDao 的 modify 方法");
        return 1;
    }
    @Override
    public void get() {
        System.out.println("正在执行 UserDao 的 get 方法");
    }
}

4.创建一个全注解的配置类

@Configuration//声明为配置类
@ComponentScan(basePackages = "net.biancheng.c") //注解扫描
@EnableAspectJAutoProxy //开启 AspectJ 的自动代理
public class AppConfig {
}

5.创建一个名为 MyAspect 的切面类

@Component // 定义成 Bean
@Aspect //定义为切面
public class MyAspect {
    @Before("execution(* net.biancheng.c.dao.UserDao.add(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置增强……" + joinPoint);
    }
    @After("execution(* net.biancheng.c.dao.UserDao.get(..))")
    public void after(JoinPoint joinPoint) {
        System.out.println("最终增强……" + joinPoint);
    }
    
    
    /**
     * 将 net.biancheng.c.dao包下的 UserDao 类中的 get() 方法 定义为一个切点
     */
    @Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.get(..))")
    public void pointCut1() {
    }
    /**
     * 将 net.biancheng.c.dao包下的 UserDao 类中的 delete() 方法 定义为一个切点
     */
    @Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.delete(..))")
    public void pointCut2() {
    }
    //使用切入点引用
    @Around("MyAspect.pointCut2()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕增强……1");
        proceedingJoinPoint.proceed();
        System.out.println("环绕增强……2");
    }
    
    
    //使用切入点表达式
    @AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.modify(..))", returning = "returnValue")
    public void afterReturning(Object returnValue) {
        System.out.println("后置返回增强……,方法返回值为:" + returnValue);
    }
}

6.创建一个MainApp的测试类

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context2 = new AnnotationConfigApplicationContext(AppConfig.class);
        UserDao userDao = context2.getBean("userDao", UserDao.class);
        userDao.add();
        userDao.modify();
        userDao.delete();
        userDao.get();
    }
}

7.测试结果

前置增强……execution(void net.biancheng.c.dao.UserDao.add())
正在执行 UserDao 的 add 方法
正在执行 UserDao 的 modify 方法
后置返回增强……,方法返回值为:1
环绕增强……1
正在执行 UserDao 的 delete 方法
环绕增强……2
正在执行 UserDao 的 get 方法
最终增强……execution(void net.biancheng.c.dao.UserDao.get())

1.7 Spring JdbcTemplate(数据库模板)

在这里插入图片描述

JdbcTemplate类的继承关系十分简单,他继承自抽象类JdbcAccessor,同时实现了JdbcOperations接口。

  1. JdbcTemplate类的直接父类是JdbcAccessor类,该类为子类提供了一些访问数据库时使用的公共属性。具体如下哦!
    • DataSource:其主要功能是获取数据库连接,具体实现是还可以引入对数据库连接的缓冲池和分布式事务的支持,它可以作为数据库访问的标准接口。
    • SQLExceptionTranslator:org.springframework,jdbc.support.SQLExceptionTranslator接口负责对SQLException进行转译工作。通过必要的设置或者获取SQLExceptionTranslator中的方法,可以使JdbcTemplate在需要的时候处理SQLException时,委托SQLExceptionTranslator的实现类来完成相应的操作
  2. JdbcOperations接口定义了在JdbcTemplate类中可以使用的操作集合,包括添加,修改,查询,删除等。

1.7.1 Spring JDBC的配置

JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。

JdbcTemplate 的全限定命名为 org.springframework.jdbc.core.JdbcTemplate,它提供了大量的查询和更新数据库的方法,如下表所示。

方法说明
public int update(String sql)用于执行新增、更新、删除等语句;sql:需要执行的 SQL 语句;args 表示需要传入到 SQL 语句中的参数。
public int update(String sql,Object… args)
public void execute(String sql)可以执行任意 SQL,一般用于执行 DDL 语句; sql:需要执行的 SQL 语句;action 表示执行完 SQL 语句后,要调用的函数。
public T execute(String sql, PreparedStatementCallback action)
public List query(String sql, RowMapper rowMapper, @Nullable Object… args)
用于执行查询语句;sql:需要执行的 SQL 语句;rowMapper:用于确定返回的集合(List)的类型;args:表示需要传入到 SQL 语句的参数。
public T queryForObject(String sql, RowMapper rowMapper, @Nullable Object… args)
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes)用于批量执行新增、更新、删除等语句; sql:需要执行的 SQL 语句;argTypes:需要注入的 SQL 参数的 JDBC 类型;batchArgs:表示需要传入到 SQL 语句的参数。
配置appliactionContext.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.xsd">


    <!--    1。配置数据库-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 数据库驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--  配置数据库的url-->
        <property name="url" value="jdbc:mysql://localhost:3306/Spring"/>
        <!--  配置数据库的账号-->
        <property name="username" value="root"/>
        <!--  配置数据库的密码-->
        <property name="password" value="root"/>
    </bean>

    <!--   2.配置JDBC模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--    默认必须使用数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--    3.配置注入类-->
    <bean id="xxx" class="xxx">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>
示例

1.创建一个名为 my-spring-jdbc-demo 的项目,并在将以下依赖导入到工程中。

  • spring-beans-5.3.13.RELEASE.jar
  • spring-context-5.3.13.RELEASE.jar
  • spring-core-5.3.13.RELEASE.jar
  • spring-expression-5.3.13.RELEASE.jar
  • commons-logging-1.2.jar
  • spring-jdbc-5.3.13.RELEASE.jar
  • spring-tx-5.3.13.RELEASE.jar
  • spring-aop-5.3.13.jar
  • mysql-connector-java-8.0.23.jar

2.创建一个User的实体类

public class User {
    private Integer userId;
    private String userName;
    private String status;
    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", status='" + status + '\'' +
                '}';
    }
}

3.创建一个UserDao接口

public interface UserDao {
    /**
     * 新增一条用户
     *
     * @param user
     * @return
     */
    int addUer(User user);
    /**
     * 更新指定的用户信息
     *
     * @param user
     * @return
     */
    int update(User user);
    /**
     * 删除指定的用户信息
     *
     * @param user
     * @return
     */
    int delete(User user);
    /**
     * 统计用户个数
     *
     * @param user
     * @return
     */
    int count(User user);
    /**
     * 查询用户列表
     *
     * @param user
     * @return
     */
    List<User> getList(User user);
    /**
     * 查询单个用户信息
     *
     * @param user
     * @return
     */
    User getUser(User user);
    /**
     * 批量增加用户
     *
     * @param batchArgs
     */
    void batchAddUser(List<Object[]> batchArgs);
}

4.创建一个UserDaoimpl实现UserDao

@Repository
public class UserDaoImpl implements UserDao {
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Resource
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    @Override
    public int addUer(User user) {
        String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
        int update = jdbcTemplate.update(sql, user.getUserName(), user.getStatus());
        return update;
    }
    @Override
    public int update(User user) {
        String sql = "UPDATE `user` SET status=? WHERE user_name=?;";
        return jdbcTemplate.update(sql, user.getStatus(), user.getUserName());
    }
    @Override
    public int delete(User user) {
        String sql = "DELETE FROM `user` where user_name=?;";
        return jdbcTemplate.update(sql, user.getUserName());
    }
    @Override
    public int count(User user) {
        String sql = "SELECT COUNT(*) FROM `user` where `status`=?;";
        return jdbcTemplate.queryForObject(sql, Integer.class, user.getStatus());
    }
    @Override
    public List<User> getList(User user) {
        String sql = "SELECT * FROM `user` where `status`=?;";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), user.getStatus());
    }
    @Override
    public User getUser(User user) {
        String sql = "SELECT * FROM `user` where `user_id`=?;";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), user.getUserId());
    }
    @Override
    public void batchAddUser(List<Object[]> batchArgs) {
        String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
        jdbcTemplate.batchUpdate(sql, batchArgs);
    }
}

5.创建一个UserService层接口

public interface UserService {
    /**
     * 新增用户数据
     *
     * @param user
     * @return
     */
    public int addUser(User user);
    /**
     * 更新用户数据
     *
     * @param user
     * @return
     */
    public int updateUser(User user);
    /**
     * 删除用户数据
     *
     * @param user
     * @return
     */
    public int deleteUser(User user);
    /**
     * 统计用户数量
     *
     * @param user
     * @return
     */
    public int countUser(User user);
    /**
     * 查询用户数据
     *
     * @param user
     * @return
     */
    public List<User> getUserList(User user);
    /**
     * 查询单个用户信息
     *
     * @param user
     * @return
     */
    public User getUser(User user);
    /**
     * 批量添加用户
     *
     * @param batchArgs
     */
    public void batchAddUser(List<Object[]> batchArgs);
}

6.创建一个UserServiceimple类实现UserService接口

@Service("userService")
public class UserServiceImpl implements UserService {
    @Resource
    private UserDao userDao;
    @Override
    public int addUser(User user) {
        return userDao.addUer(user);
    }
    @Override
    public int updateUser(User user) {
        return userDao.update(user);
    }
    @Override
    public int deleteUser(User user) {
        return userDao.delete(user);
    }
    @Override
    public int countUser(User user) {
        return userDao.count(user);
    }
    @Override
    public List<User> getUserList(User user) {
        return userDao.getList(user);
    }
    @Override
    public User getUser(User user) {
        return userDao.getUser(user);
    }
    @Override
    public void batchAddUser(List<Object[]> batchArgs) {
        userDao.batchAddUser(batchArgs);
    }
    @Override
    public int countOfUserByName(User user) {
        return userDao.countOfUserByName(user);
    }
    @Override
    public User getUserByUserId(User user) {
        return userDao.getUserByUserId(user);
    }
}

7.创建一个MainApp的类

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml");
        UserService userService = context2.getBean("userService", UserService.class);
        User user = new User();
        user.setUserName("小张");
        user.setStatus("离线线");
        //新增一个用户
        int i = userService.addUser(user);
        System.out.println("新增用户成功!");
        User user1 = new User();
        user1.setUserName("小张");
        user1.setStatus("在线");
        int u = userService.updateUser(user1);
        System.out.println("修改用户成功");
        List<Object[]> batchArgs = new ArrayList<>();
        Object[] o1 = {"小明", "在线"};
        Object[] o2 = {"小龙", "离线"};
        Object[] o3 = {"小林", "在线"};
        Object[] o4 = {"小李", "在线"};
        batchArgs.add(o1);
        batchArgs.add(o2);
        batchArgs.add(o3);
        batchArgs.add(o4);
        userService.batchAddUser(batchArgs);
        System.out.println("批量增加完毕");
        User user2 = new User();
        user2.setStatus("在线");
        int i1 = userService.countUser(user2);
        System.out.println("在线用户的个数为:" + i1);
        List<User> userList = userService.getUserList(user2);
        System.out.println("在线用户列表查询成功!");
        for (User user4 : userList) {
            System.out.println("用户 ID:" + user4.getUserId() + ",用户名:" + user4.getUserName() + ",状态:" + user4.getStatus());
        }
    }
}

注:前提先创建一个数据库

1.8 Spring事务(Transaction)

事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。

事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。

  • 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
  • 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
  • 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
  • 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。

事务允许我们将几个或一组操作组合成一个要么全部成功、要么全部失败的工作单元。如果事务中的所有的操作都执行成功,那自然万事大吉。但如果事务中的任何一个操作失败,那么事务中所有的操作都会被回滚,已经执行成功操作也会被完全清除干净,就好像什么事都没有发生一样。

1.8.1事务管理方式

Spring 支持以下 2 种事务管理方式。

事务管理方式说明
编程式事务管理编程式事务管理是通过编写代码实现的事务管理。 这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。
声明式事务管理Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。

  • 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
  • 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。

Spring 的声明式事务管理主要通过以下 2 种方式实现:

1.8.2 事务管理器

Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。

在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

该接口中各方法说明如下:

名称说明
TransactionStatus getTransaction(TransactionDefinition definition)用于获取事务的状态信息
void commit(TransactionStatus status)用于提交事务
void rollback(TransactionStatus status)用于回滚事务

Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。

实现类说明
org.springframework.jdbc.datasource.DataSourceTransactionManager使用 Spring JDBC 或 iBatis 进行持久化数据时使用。
org.springframework.orm.hibernate3.HibernateTransactionManager使用 Hibernate 3.0 及以上版本进行持久化数据时使用。
org.springframework.orm.jpa.JpaTransactionManager使用 JPA 进行持久化时使用。
org.springframework.jdo.JdoTransactionManager当持久化机制是 Jdo 时使用。
org.springframework.transaction.jta.JtaTransactionManager使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。

这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。

1.8.3 TransactionDefinition 接口

Spring 将 XML 配置中的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。

TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下。

public interface TransactionDefinition {
    int getPropagationBehavior();
    int getIsolationLevel();
    String getName();
    int getTimeout();
    boolean isReadOnly();
}

该接口中方法说明如下。

方法说明
String getName()获取事务的名称
int getIsolationLevel()获取事务的隔离级别
int getPropagationBehavior()获取事务的传播行为
int getTimeout()获取事务的超时时间
boolean isReadOnly()获取事务是否只读

1.8.4 事务的隔离级别

事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。

在实际应用中,经常会出现多个事务同时对同一数据执行不同操作,来实现各自的任务的情况。此时就有可能导致脏读、幻读以及不可重复读等问题的出现。

在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。

Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。

方法说明
ISOLATION_DEFAULT使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
ISOLATION_READ_COMMITTEDOracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
ISOLATION_REPEATABLE_READMySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
ISOLATION_SERIALIZABLE完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读

关于事务隔离级别、脏读、幻读、不可重复度等概念的详细介绍,请阅读《数据库事务隔离级别》一节。

1.8.5 事务的传播行为

事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。例如,事务方法 A 在调用事务方法 B 时,B 方法是继续在调用者 A 方法的事务中运行呢,还是为自己开启一个新事务运行,这就是由事务方法 B 的事务传播行为决定的。

事务方法指的是能让数据库表数据发生改变的方法,例如新增数据、删除数据、修改数据的方法。

Spring 提供了以下 7 种不同的事务传播行为。

名称说明
PROPAGATION_MANDATORY支持当前事务,如果不存在当前事务,则引发异常。
PROPAGATION_NESTED如果当前事务存在,则在嵌套事务中执行。
PROPAGATION_NEVER不支持当前事务,如果当前事务存在,则引发异常。
PROPAGATION_NOT_SUPPORTED不支持当前事务,始终以非事务方式执行。
PROPAGATION_REQUIRED默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。
PROPAGATION_REQUIRES_NEW创建新事务,如果已经存在事务则暂停当前事务。
PROPAGATION_SUPPORTS支持当前事务,如果不存在事务,则以非事务方式执行。

1.8.6 TransactionStatus 接口

TransactionStatus 接口提供了一些简单的方法,来控制事务的执行、查询事务的状态,接口定义如下。

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    boolean isCompleted();
}

该接口中各方法说明如下。

名称说明
boolean hasSavepoint()获取是否存在保存点
boolean isCompleted()获取事务是否完成
boolean isNewTransaction()获取是否是新事务
boolean isRollbackOnly()获取事务是否回滚
void setRollbackOnly()设置事务回滚

1.8.7 Spring基于XML实现事务管理

Spring 声明式事务管理是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建(或加入)一个事务,在执行完目标方法后,根据执行情况提交或者回滚事务。

声明式事务最大的优点就是对业务代码的侵入性低,可以将业务代码和事务管理代码很好地进行解耦。

Spring 实现声明式事务管理主要有 2 种方式:

  • 基于 XML 方式的声明式事务管理。
  • 通过 Annotation 注解方式的事务管理。
1.8.7.1 引入 tx 命名空间

Spring 提供了一个 tx 命名空间,借助它可以极大地简化 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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

**注意:**由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。

1.8.7.2. 配置事务管理器
<!--配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <!--数据库连接地址-->
    <property name="url" value="xxx"/>
    <!--数据库的用户名-->
    <property name="username" value="xxx"/>
    <!--数据库的密码-->
    <property name="password" value="xxx"/>
    <!--数据库驱动-->
    <property name="driverClassName" value="xxx"/>
</bean>
<!--配置事务管理器,以 JDBC 为例-->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

1.8.7.3. 配置事务通知

在 Spring 的 XML 配置文件中配置事务通知,指定事务作用的方法以及所需的事务属性。

<!--配置通知-->
<tx:advice id="tx-advice" transaction-manager="transactionManager">
    <!--配置事务参数-->
    <tx:attributes>
        <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
    </tx:attributes>
</tx:advice>
1.8.7.3.1 事务管理器配置

当我们使用 tx:advice 来声明事务时,需要通过 transaction-manager 参数来定义一个事务管理器,这个参数的取值默认为 transactionManager。

如果我们自己设置的事务管理器(第 2 步中设置的事务管理器 id)恰好与默认值相同,则可以省略对改参数的配置。

<tx:advice id="tx-advice" >
    <!--配置事务参数-->
    <tx:attributes>
        <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
    </tx:attributes>
</tx:advice>

但如果我们自己设置的事务管理器 id 与默认值不同,则必须手动在 tx:advice 元素中通过 transaction-manager 参数指定。

1.8.7.3.2 事务属性配置

对于tx:advice 来说,事务属性是被定义在tx:attributes 中的,该元素可以包含一个或多个 tx:method 元素。

tx:method 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。

事务属性说明
propagation指定事务的传播行为。
isolation指定事务的隔离级别。
read-only指定是否为只读事务。
timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。
1.8.7.4. 配置切点切面

tx:advice 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 tx:advice 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。

在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。

<!--配置切点和切面-->
<aop:config>
    <!--配置切点-->
    <aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
</aop:config>
示例:

1.创建数据库文件

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint DEFAULT NULL COMMENT '用户id',
  `total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
  `used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
  `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', '1', '1000', '0', '1000');
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_id` varchar(200) NOT NULL,
  `user_id` varchar(200) NOT NULL COMMENT '用户id',
  `product_id` varchar(200) NOT NULL COMMENT '产品id',
  `count` int DEFAULT NULL COMMENT '数量',
  `money` decimal(11,0) DEFAULT NULL COMMENT '金额',
  `status` int DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `storage`;
CREATE TABLE `storage` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `product_id` bigint DEFAULT NULL COMMENT '产品id',
  `total` int DEFAULT NULL COMMENT '总库存',
  `used` int DEFAULT NULL COMMENT '已用库存',
  `residue` int DEFAULT NULL COMMENT '剩余库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `storage` VALUES ('1', '1', '100', '0', '100');

2.创建一个Java项目,导入相应的jar包

  • spring-beans-5.3.13.RELEASE.jar
  • spring-context-5.3.13.RELEASE.jar
  • spring-core-5.3.13.RELEASE.jar
  • spring-expression-5.3.13.RELEASE.jar
  • commons-logging-1.2.jar
  • spring-jdbc-5.3.13.RELEASE.jar
  • spring-tx-5.3.13.RELEASE.jar
  • spring-aop-5.3.13.jar
  • mysql-connector-java-8.0.23.jar
  • aspectjweaver-1.9.7.jar
  • spring-aspects-5.3.13.jar

3.创建一个名为 Order 的实体类

public class Order {
    //自增 id
    private Long id;
    //订单 id
    private String orderId;
    //用户 id
    private String userId;
    //商品 id
    private String productId;
    //订单商品数量
    private Integer count;
    //订单金额
    private BigDecimal money;
    //订单状态
    private Integer status;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getOrderId() {
        return orderId;
    }
    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getProductId() {
        return productId;
    }
    public void setProductId(String productId) {
        this.productId = productId;
    }
    public Integer getCount() {
        return count;
    }
    public void setCount(Integer count) {
        this.count = count;
    }
    public BigDecimal getMoney() {
        return money;
    }
    public void setMoney(BigDecimal money) {
        this.money = money;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
}

4.创建一个名为 Account 的实体类

public class Account {
    //自增 id
    private Long id;
    //用户 id
    private String userId;
    //账户总金额
    private BigDecimal total;
    //已用账户金额
    private BigDecimal used;
    //剩余账户金额
    private BigDecimal residue;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public BigDecimal getTotal() {
        return total;
    }
    public void setTotal(BigDecimal total) {
        this.total = total;
    }
    public BigDecimal getUsed() {
        return used;
    }
    public void setUsed(BigDecimal used) {
        this.used = used;
    }
    public BigDecimal getResidue() {
        return residue;
    }
    public void setResidue(BigDecimal residue) {
        this.residue = residue;
    }
}

4.创建一个名为 Storage 的实体类

public class Storage {
    //自增 id
    private Long id;
    //商品 id
    private String productId;
    //商品库存总数
    private Integer total;
    //已用商品数量
    private Integer used;
    //剩余商品数量
    private Integer residue;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getProductId() {
        return productId;
    }
    public void setProductId(String productId) {
        this.productId = productId;
    }
    public Integer getTotal() {
        return total;
    }
    public void setTotal(Integer total) {
        this.total = total;
    }
    public Integer getUsed() {
        return used;
    }
    public void setUsed(Integer used) {
        this.used = used;
    }
    public Integer getResidue() {
        return residue;
    }
    public void setResidue(Integer residue) {
        this.residue = residue;
    }
}

5.创建一个名为 OrderDao 的接口

public interface OrderDao {
    /**
     * 创建订单
     * @param order
     * @return
     */
    int createOrder(Order order);
    /**
     * 修改订单状态
     * 将订单状态从未完成(0)修改为已完成(1)
     * @param orderId
     * @param status
     */
    void updateOrderStatus(String orderId, Integer status);
}

6.创建一个名为 AccountDao 的接口

public interface AccountDao {
    /**
     * 根据用户查询账户金额
     * @param userId
     * @return
     */
    Account selectByUserId(String userId);
    /**
     * 扣减账户金额
     * @param userId
     * @param money
     * @return
     */
    int decrease(String userId, BigDecimal money);
}

7.创建一个名为 StorageDao 的接口

public interface StorageDao {
    /**
     * 查询商品的库存
     * @param productId
     * @return
     */
    Storage selectByProductId(String productId);
    /**
     * 扣减商品库存
     * @param record
     * @return
     */
    int decrease(Storage record);
}

8.创建 OrderDao 的实现类 OrderDaoImpl

@Repository
public class OrderDaoImpl implements OrderDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public int createOrder(Order order) {
        String sql = "insert into `order` (order_id,user_id, product_id, `count`, money, status) values (?,?,?,?,?,?)";
        int update = jdbcTemplate.update(sql, order.getOrderId(), order.getUserId(), order.getProductId(), order.getCount(), order.getMoney(), order.getStatus());
        return update;
    }
    @Override
    public void updateOrderStatus(String orderId, Integer status) {
        String sql = " update `order`  set status = 1 where order_id = ? and status = ?;";
        jdbcTemplate.update(sql, orderId, status);
    }
}

9.创建 AccountDao 的实现类 AccountDaoImpl

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Account selectByUserId(String userId) {
        String sql = "  select * from account where user_id = ?";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), userId);
    }
    @Override
    public int decrease(String userId, BigDecimal money) {
        String sql = "UPDATE account SET residue = residue - ?, used = used + ? WHERE user_id = ?;";
        return jdbcTemplate.update(sql, money, money, userId);
    }
}

10.创建 StorageDao 的实现类 StorageDaoImpl

@Repository
public class StorageDaoImpl implements StorageDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Storage selectByProductId(String productId) {
        String sql = "select *   from storage where product_id = ?";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Storage>(Storage.class), productId);
    }
    @Override
    public int decrease(Storage record) {
        String sql = " update storage set  used =? ,residue=? where product_id=?";
        return jdbcTemplate.update(sql, record.getUsed(), record.getResidue(), record.getProductId());
    }
}

11.创建一个名为 OrderService 的接口

public interface OrderService {
    /**
     * 创建订单
     * @param order
     * @return
     */
    public void createOrder(Order order);
}

11.创建 OrderService 的实现类 OrderServiceImpl

@Service("orderService")
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private StorageDao storageDao;
    @Override
    public void createOrder(Order order) {
        //自动生成订单 id
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String format = df.format(new Date());
        String orderId = order.getUserId() + order.getProductId() + format;
        System.out.println("自动生成的订单 id 为:" + orderId);
        order.setOrderId(orderId);
        System.out.println("开始创建订单数据,订单号为:" + orderId);
        //创建订单数据
        orderDao.createOrder(order);
        System.out.println("订单数据创建完成,订单号为:" + orderId);
        System.out.println("开始查询商品库存,商品 id 为:" + order.getProductId());
        Storage storage = storageDao.selectByProductId(order.getProductId());
        if (storage != null && storage.getResidue().intValue() >= order.getCount().intValue()) {
            System.out.println("商品库存充足,正在扣减商品库存");
            storage.setUsed(storage.getUsed() + order.getCount());
            storage.setResidue(storage.getTotal().intValue() - storage.getUsed());
            int decrease = storageDao.decrease(storage);
            System.out.println("商品库存扣减完成");
        } else {
            System.out.println("警告:商品库存不足,正在执行回滚操作!");
            throw new RuntimeException("库存不足");
        }
        System.out.println("开始查询用户的账户金额");
        Account account = accountDao.selectByUserId(order.getUserId());
        if (account != null && account.getResidue().intValue() >= order.getMoney().intValue()) {
            System.out.println("账户金额充足,正在扣减账户金额");
            accountDao.decrease(order.getUserId(), order.getMoney());
            System.out.println("账户金额扣减完成");
        } else {
            System.out.println("警告:账户余额不足,正在执行回滚操作!");
            throw new RuntimeException("账户余额不足");
        }
        System.out.println("开始修改订单状态,未完成》》》》》已完成");
        orderDao.updateOrderStatus(order.getOrderId(), 0);
        System.out.println("修改订单状态完成!");
    }
}

12.,创建一个 jdbc.properties,

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring-tx-db
jdbc.username=root
jdbc.password=root
注:-------------------->》完整的applicationContext.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--开启组件扫描-->
    <context:component-scan base-package="net.biancheng.c"></context:component-scan>
    <!--引入 jdbc.properties 中的配置-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--定义数据源 Bean-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库连接地址-->
        <property name="url" value="${jdbc.url}"/>
        <!--数据库的用户名-->
        <property name="username" value="${jdbc.username}"/>
        <!--数据库的密码-->
        <property name="password" value="${jdbc.password}"/>
        <!--数据库驱动-->
        <property name="driverClassName" value="${jdbc.driver}"/>
    </bean>
    <!--定义 JdbcTemplate Bean-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--将数据源的 Bean 注入到 JdbcTemplate 中-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置通知-->
    <tx:advice id="tx-advice">
        <!--配置事务参数-->
        <tx:attributes>
            <!--name 指定哪些方法上添加事务-->
            <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
        </tx:attributes>
    </tx:advice>
    <!--配置切点和切面-->
    <aop:config>
        <!--配置切点-->
        <aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
    </aop:config>
</beans>

15.创建一个名为 MainApp 的类

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml");
        OrderService orderService = context2.getBean("orderService", OrderService.class);
        Order order = new Order();
        //设置商品 id
        order.setProductId("1");
        //商品数量为 30
        order.setCount(30);
        //商品金额为 600
        order.setMoney(new BigDecimal(600));
        //设置用户 id
        order.setUserId("1");
        //订单状态为未完成
        order.setStatus(0);
        orderService.createOrder(order);
    }
}

1.8.8 Spring基于注解实现事务管理

我们通过 tx:advice 元素极大的简化了 Spring 声明式事务所需的 XML 配置。但其实我们还可以通过另一种方式进行进一步的简化,那就是“使用注解实现事务管理”。

在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用注解实现,以进一步降低代码之间的耦合度。下面我们就来介绍下,通过注解是如何实现声明式事务管理。

1.8.8.1. 开启注解事务

tx 命名空间提供了一个 tx:annotation-driven 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。

tx:annotation-driven 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。

<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

与 tx:advice 元素一样,tx:annotation-driven 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。

<tx:annotation-driven/>

通过 <tx:annotation-driven> 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。
1.8.8.2. 使用 @Transactional 注解

@Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。

@Transactional
public class XXX {
    @Transactional
    public void A(Order order) {
    ……
    }
    public void B(Order order) {
    ……
    }
}

若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。

Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。

@Transactional 注解包含多个属性,其中常用属性如下表。

事务属性说明
propagation指定事务的传播行为。
isolation指定事务的隔离级别。
read-only指定是否为只读事务。
timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。

1.9 Spring整合日志框架Log4j2

对于一款软件而言,日志记录都是十分重要的。它不仅能够监控程序的运行情况,周期性的记录到文件中,还能够跟踪程序中代码的运行轨迹,向文件或控制台打印代码的调试信息。当程序出现错误时,日志记录可以帮助开发人员及时定位问题,因此对开发人员来说,日志记录更是尤为重要。

Spring 5 框架自带了通用的日志封装,但是我们依然可以整合其他的日志框架对日志进行记录,其中最广为人知的就是大名鼎鼎的 Log4j。

Log4j 是 Apache 提供的一款开源的强有力的 Java 日志记录工具。它可以通过配置文件灵活、细致地控制日志的生成过程,例如日志级别、日志的输出类型、日志的输出方式以及输出格式等。

Log4j 共有两个大版本,如下表所示。

版本时间说明
Log4j 1.x1999 年至 2015 年即我们常说的 Log4j, 它于 1999 年首次发布,就迅速成为有史以来最常用的日志框架。 2015 年 8 月 5 日,Apache Logging Services 宣布 Log4j 1.x 生命周期结束,其代码库不再发布更新,并鼓励用户升级到 Log4j 2.x。
Log4j 2.x2014 年至今即我们常说的 Log4j2,2014 年 Log4j 2.x 作为 Log4j 1.x 的替代品发布。 Log4j 2.x 是对 Log4j 1.x 的重大升级,它完全重写了 Log4j 的日志实现,比 Log4j 1.x 效率更高、更可靠且更易于开发和维护。此外,Log4j 2.x 还对 Logback 进行了许多改进,修复了 Logback 架构中的一些固有问题,目前已经更新到 2.17.1 版本。

Spring 整合 Log4j2

Spring 5 是基于 Java 8 实现的,其自身作了不少的优化,将许多不建议使用的类和方法从代码库中删除,其中就包括了 Log4jConfigListener(Spring 加载 Log4j 配置的监听器)。因此从 Spring 5 开始就不在支持对 Log4j 的整合,而更加推荐我们通过 Log4j2 进行日志记录。

下面我们们就来介绍下 Spring 是如何整合 Log4j2 的。

1.新建一个名为 my-spring-log4j-demo 的 Spring 项目,并将与 Spring 相关的 Jar 包导入到该项目中。

2.使用浏览器访问 Log4j2 官网,点击左侧导航栏中的 Download,跳转到 Log4j2 的下载页面。

img
图1:Log4j2 下载页面

3.我们根据自身操作系统的不同,选择不同的压缩包

4.对下载完成的压缩包进行解压,并将以下 3 个依赖包导入到 my-spring-log4j-demo 项目中。

  • log4j-api-2.17.1.jar
  • log4j-core-2.17.1.jar
  • log4j-slf4j18-impl-2.17.1.jar

5.此外,我们还需要向 my-spring-log4j-demo 项目中导入一个 slf4j-api-xxx.jar ,但该依赖包的版本是有限制的。

此前,我们下载的 Log4j2 的依赖包中有一个 log4j-slf4j18-impl-2.17.1.jar,它是 Log4j2 提供的绑定到 SLF4J 的配置器。

Log4j2 提供了以下 2 个适配器:

  • log4j-slf4j-impl 应该与 SLF4J 1.7.x 版本或更早版本一起使用。
  • log4j-slf4j18-impl 应该与 SLF4J 1.8.x 版本或更高版本一起使用。

6.创建一个名为 log4j2.xml 的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!-- Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置,当设置成 trace 时,可以看到 log4j2 内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的 appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义 logger,只有定义了 logger 并引入的 appender,appender 才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root 作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

7.创建一个名为 HelloLog4j 的 Java 类

public class HelloLog4j {
    private static final Logger log = LoggerFactory.getLogger(HelloLog4j.class);
    private String message;
    public void setMessage(String message) {
        this.message = message;
    }
    public void getMessage() {
        log.info("消息为:" + message);
    }
}

8.创建一个 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">
   
    <bean id="helloLog4j" class="net.biancheng.c.HelloLog4j">
        <property name="message" value="Hello,Spring!"/>
    </bean>
</beans>

9.创建一个 MainApp 的类

public class MainApp {
    private static final Logger log = LoggerFactory.getLogger(MainApp.class);
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        log.info("正在从容器中获取 HelloLog4j 的 Bean");
        HelloLog4j obj = context.getBean("helloLog4j", HelloLog4j.class);
        obj.getMessage();
        log.info("代码执行完成!");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值