Spring基础

spring讲义

spring官网

下文中所有项目都是通过 maven 构建的quickstart项目

csdn比较好的博客

1.什么是Spring框架

  • 它是一个容器,帮助解决企业开发的难度,减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系。

  • 它是整合其它框架的框架,

  • 它的核心是IOC和AOP (控制反转,面向切面编程)能够实现模块之间,类之间的解耦合

  • 它由20多个模块构成。

  • 它在很多领域都提供优秀的解决方案.

    依赖:classA中使用classB的属性或者方法,叫做classA依赖classB

    我们课程里学Spring,SpringMVC,SpringBoot,SpringCloud

什么是spring:

  • 是一个框架,核心技术是 ioc,aop,实现解耦合
  • spring是一个容器,容器中存放的是java对象,需要做的是把对象放入到容器中

怎么使用spring:

  • spring是一个容器,把项目中用的对象放入到容器中
  • 让容器完成对象的创建,对象之间关系的管理(属性赋值:基础类型和引用类型)
  • 我们在程序中从容器中获取 要使用的对象

什么样的对象放到容器中:

  • dao类,service类,controller类,工具类
  • spring中的对象默认都是单例的,在容器中叫这个名称的对象(id)只有一个

不放入到spring容器中的对象:

  • 实体类对象,实体类数据来自数据库的
  • servlet,listener,filter等。交给tomcat服务器创建对象
框架怎么学

框架是一个软件,其它人写好的软件。

  1. 知道框架能做什么。 mybatis ---- 访问数据库,对表中的数据执行增删改查
  2. 框架的语法:框架要完成一个功能,需要一定的步骤支持
  3. 框架的内部实现,框架内部怎么做,原理是什么
  4. 手写框架、

2.Spring的特点

  1. 轻量级

    由20多个模块构成,每个jar包都很小,小于1M,核心包也就3M左右。

    对代码无污染。

  2. 面向接口编程

    使用接口,就是面向灵活,项目的可扩展性,可维护性都极高。

    接口不关心实现类的类型。使用时接口指向实现类,切换实现类即可切换整个功能。

  3. AOP:面向切面编程

    就是将公共的,通用的,重复的代码单独开发,在需要的时候反织回去(设计成接口,接口指向实现类)。底层的原理是动态代理

  4. 整合其它框架

    它整合后使其它框架更易用.

  5. IOC(inversion of control) 控制反转,是一个理论,概念,思想

    控制:创建对象,对象的属性赋值,对象间的关系管理

    反转:把对象的创建,赋值,管理工作交给代码之外的容器实现,也就是对象的创建由其他外部资源完成

    目的:减少代码的改动,实现不同的功能,实现解耦合

    spring使用依赖注入(DI)实现了IOC的功能

java创建对象的方式
  1. 构造器
  2. 反射
  3. 序列化(对象保存到磁盘)
  4. 克隆
  5. ioc:容器创建对象
  6. 动态代理

ioc的体现:

servlet:

  1. 创建类继承 HttpServlet

  2. 在web.xml中注册servlet(见ervlet-jsp笔记:使用标签)

  3. 开发人员没有创建Servlet对象,

  4. Servlet对象由Tomcat服务器创建的

    ​ tomcat作为容器,里面存放的有Servlet对象,Listener,Filter对象

IOC的技术实现

DI是IOC的技术实现

DI(依赖注入):只需要在程序中提供要使用对象的名称就可以。至于对象的创建和赋值,查找都由容器内部实现

spring是使用di实现ioc的功能,spring底层创建对象,使用的是反射机制。

ioc能够实现业务对象之间的解耦合,例如 service 和dao 对象之间的解构合

DI的实现:

  1. spring的配置文件中,使用标签和属性完成,叫做基于XML的di实现(第4节)
  2. 使用spring中的注解,完成属性赋值,叫做基于注解的di实现(第6节)

基于xml的DI给属性赋值(第4节)

  1. set注入(设值注入):spring调用类的set方法,在set方法中可以实现属性的赋值。(需提供无参构造器和set方法)
  2. 构造注入:spring调用类的含参构造器,创建对象,在构造方法中完成赋值

3.什么是IOC

概念,语法

控制反转IOC(Inversion of Control)是一个概念,是一种思想。DI是这种思想的具体实现

由Spring容器进行对象的创建和依赖注入(给属性赋值)。程序员在使用时直接取出使用.

当spring应用了IOC,一个对象依赖的其他对象会通过被动的方式传递进来(由容器创建并传值。依赖注入DI),而不是这个对象自己创建或查找依赖对象

控制:创建对象,对象的属性赋值,对象间的关系管理

  1. 正转(使用无参构造,程序员通过set方法为对象赋值):

    由程序员进行对象的创建和依赖注入称为正转。程序员说了算.

    Student stu = new Student();   ===>程序员创建对象
    stu.setName("张三");           ===>程序员进行赋值
    stu.setAge(22);
    
  2. 反转(提供无参构造和set方法,容器自动创建对象):

由Spring容器创建对象和依赖注入(属性赋值)称为反转,将控制权从程序员手中夺走,交给Spring容器,称为反转.。

容器说了算.

// applicationContext.xml

<!--告诉spring创建对象
 	声明bean,就是告诉spring要创建某个类的对象
	id:对象的自定义名称,唯一值,spring通过这个名称找到对象
	class:类的全限定名(不能是接口,与mybatis区分开,因为spring是反射创建对象,必须使用类
	property:对象属性赋值。name为类中成员变量名,value为类中基本类型赋值,引用类型用 ref
	
spring就完成:Student stu = new Student();  // 找com.bjpowernode.pojo包下Student类的无参构造器
spring把创建好的对象放到map中,spring框架有一个map存放对象
	  springMap.put(id的值,对象)  // String类型的对象名, Object类型的对象(Bean对象)
	  例如:springMap.put("stu", new Student());

一个bean标签声明一个对象
 -->

<!--
	spring使用xml解析器解析这些数据,利用 java.bean.PropertyEditor完成类型转换。
	从String类型到所需要的参数值类型
-->
<beans>
	<bean id="stu" class="com.bjpowernode.pojo.Student">     ===>Spring容器负责对象的创建
    	<property name="name" value="张三"> <!-- setName("张三");-->===>Spring容器依赖注入值
“   	<property name="age" value="22"> 
	</bean>
            
    <!-- spring 不仅能创建一个非自定义类的对象,也能创建一个存在的某个类的对象 -->
    <bean id="mydate" class="java.util.Date">
        <propety name="time" value="83641235"></propety> <!-- 调用Date类的 setTime()方法-->
    </bean>
</beans>
 
<!-- 
	spring的配置文件:
		1. beans:根标签,spring把java对象成为bean
		2. spring-beans.xsd是约束文件,和mybatis指定的 dtd文件是一样的
-->
ioc容器的基础(补)

什么是bean?

  • 在spring中,由spring IOC容器管理的对象叫做Bean。也就是在 applicationContext.xml中配置的对象
  • Bean是由Spring IOC容器实例化,组装和以其他方式管理的对象
  • Bean以及它们之间的依赖关系通过容器配置元数据(xml,java注解 or java代码)来反映

ban对象之间的依赖关系叫做依赖

  • 依赖注入:一个对象依赖的其他对象会通过被动的方式传递进,由容器传

  • 依赖注入两种方式

    • setter注入

      • p命名空间注入:本质还是setter注入,但是使xml配置更简洁

        在头文件中加入:xmlns:p="http://www.springframework.org/schema/p"
        
        <bean id="user" class="com.zjs.User" p:userId="11" p:name="张三" p:age="20"></bean>
        
    • 构造方法注入

提供了IOC容器的基本功能,是Spring框架的IOC容器的基础

  • org.springframework.beans

  • org.springframework.context

IOC容器中两个重要的接口

  • BeanFactory

    • 提供了一种能够管理任何类型对象的高级配置机制
    • 工厂模式的具体实现
    • 使用IOC 将配置 和 代码分离
    • BeanFactory容器实例化后并不会自动实例化Bean,只有当Bean被使用时, BeanFactory容器才会对该Bean进行实例化与依赖关系的配置(依赖注入)
  • ApplicationContext

    • 是 BeanFactory的子接口

    • 子类

      • ClassPathXmlApplicationContext
        • 从类路径中的xml文件载入上下文信息
        • 常用,移植性强
      • FileSystemXmlApplicationContext
        • 从文件系统中的xml文件载入上下文定义信息
    • ApplicationContext容器实例化后会自动对所有单实例Bean进行实例化与依赖关系的配置,使之处于待用状态

    • spring中有过以下代码

      ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
      

      spring通过读取Bean的配置完成创建Bean工厂,常用 xml,注解 配置元数据。其中 bean标签用于配置javaBean对象。

项目1

项目1

  1. 项目构建

    • 新建空项目

      • 在 project structure 中配置jdk的版本。
      • 在settings中搜索maven,配置maven本地的settings.xml文件和maven仓库
    • 添加新模块(maven构建的quickstart项目)

      • 修改该模块的目录

      • 在pom.xml文件中添加配置(添加spring的依赖,添加资源文件指定(见mybatis笔记)) 上依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        
      • 刷新maven,在 src/main/resources 下新建 XML Configuration File(Mybatis中新建的是普通的 File)。命名为applicationContext.xml(spring的规范)上配置

        • 在其中配置javaBean对象,供容器创建对象使用(底层用的是反射)
  2. 在pojo包下创建实体类(使用set注入:必须包含无参构造器,get/set方法),在applicationContext.xml中进行如下 第4节 的配置(目的是让容器为我们创建对象)

  3. 进行测试(test)

    //由spring容器进行容器的创建(容器一启动,就创建xml配置文件下bean标签中的所有对象)
    // 如果想从spring容器中取出对象,则需要先创建容器对象,并启动才可以取对象
    
    // 配置文件可以放在某个目录下,根据 目录名/xml 页可以取到该对象。这里便放到了 src/main/resources/s01包下
    
    // 创建表示spring容器的对象,ApplicationContext接口
    // ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件,进行对象的创建(在这个配置文件中有 bean对象的定义)
    // 也可以写成 "classpath:s01/applicationContext.xml"
    // classpath表示类的根路径;classpath*表示除了类路径之外,同时也会查找依赖库(.jar)下的目录
    // 类路径 指的是 编译后的class文件的目录,在web项目中是target/WEB-INF/classes目录
    ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
    
    // 从容器中获取某个对象,通过 bean标签中的id属性值
    // getBean中内容为 配置文件<bean>标签中的id属性值
    Student stu = (Student)ac.getBean("stu");
    Sysetm.out.println(stu);
    
    
    // 使用spring提供的方法,获取容器中定义的对象的数量,即bean标签的个数
    ac.getBeanDefinitionCount();
    // 容器中每个定义的对象的名称,即 xml中id属性的值
    ac.getBeanDefinitionNames();
    

    切记:

    • Spring容器在启动时,就创建配置文件applicationContext.xml中所有的对象(一个bean就是一个对象)
    • spring创建对象:默认调用的是无参数构造方法
4. 基于xml的IOC (适用于改动比较多)

项目1

以下代码都是在 上文中的applicationContext.xml中写的

  1. 创建对象(容器启动时创建)

    <!-- 声明bean,就是告诉spring要创建某个类的对象  
    		id 为对象名。对象的自定义名称,唯一值。spring通过这个名称找到对象
     		name 为类中属性的名称;
     		class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类),也就是spring创建的对象的类型。后续会用到别名
    -->
    <bean id="stu" class="com.bjpowernode.pojo.Student"></bean>
    
  2. 给创建的对象赋值

    1. 使用setter注入。(无参构造器,提供set方法)

      Student stu = new Student();
      stu.setName("李四");
      stu.setAge("22");
      

      spring创建对象,默认调用的是无参数构造方法,然后调用类的set方法,在set方法中完成属性赋值(在set方法中进行任何操作均可,由开发人员控制)

      注入分为简单类型注入和引用类型注入(注入:即赋值)

      • 简单类型注入值使用value属性(Spring中规定:基本数据类型 + String类型 称为简单类型)

      • 引用类型注入值使用ref属性

        • 引用类型自动注入
      • 必须要注意:使用setter注入必须提供无参的构造方法,必须提供setXXX()方法.

        <!--创建学生对象:com.zjs.pojo2包-->
        
        <bean id="stu" class="com.bjpowernode.pojo2.Student">
            ===>简单类型注入:用 value
            <property name="name" value="李四"></property>  <!-- setName("李四") -->  
            <property name="age" value="22"></property> <!-- setAge("22") -->
            <!--
         引用类型的自动注入:分为 按类型注入 和 按名称注入(采用自动注入,下面这行依赖注入的语句可以不要):
        
        在 bean标签中有一个 autowire属性,值为 byType相当于注解@Autowired,
                                       值为 byName 相当于注解@Autowired 
                                                           @Qualifier("指定名称")
        										   也相当于@Resource(name="指定名称")
        
        	byType(按类型注入): java类中引用类型的数据类型和spring配置文件中bean的class属性是同					  源关系(见第6节),这样的bean能够赋值给引用类型
        			注:在xml中使用按类型注入,若碰到父子类等有多个可注入的对象时,会报 expected                   single matching bean but found 2 错误(项目4 s03是同类型问题注解版本				的,可由其重写问题的测试代码)		
                       与第6节的注解方式不同,使用注解遇到该问题,会默认注入父类,而不会报错
         
            byName(按名称注入): java类中引用类型的属性名和spring配置文件中bean中id名称一样且数据                        类型一致(引用类型和bean的class属性一致),这样的容器中的bean						  (java对象),spring能够赋值给引用类型
        	 -->
            
             ===>引用类型注入:用ref
            <property name="school" ref="school"></property> <!-- setSchool(school),这里传的值是对象,即bean标签中id属性值,也就是 school对象。这个对象是spring容器为我们创建好了的,是我们在xml中配置好了,所以spring为我们创建 -->
        </bean>
        
        <!--创建学校对象-->
        <bean id="school" class="com.bjpowernode.pojo2.School">
            <property name="name" value="清华大学"></property><!-- setName("清华大学") -->
            <property name="address" value="海淀区"></property><!-- setAddress("海淀区") -->
        </bean>
        
        
        <bean id="mydate" class="java.util.Date">
        </bean>
        

        测试文件

        //注意:为了避免冲突,将该段示例的 xml文件放至 src/main/resources/s02包下
        @Test
        public void testSchoolSpring() {
            ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
            //School school = (School) ac.getBean("school");
            //System.out.println(school);
            Student stu = (Student) ac.getBean("stu");
            System.out.println(stu);
        }
        
        @Test
        public void testMyDate(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
            Date date = (Date)ac.getBean("mydate");
            System.out.println("date:" date);
        }
        
        /**
         单元测试:一个工具类库,做测试方法使用
         单元:指定的是方法,一个类中有很多方法,一个方法称为单元
         使用单元测试必须在pom.xml中加入 junit依赖。
         创建测试类:src/test/java目录中创建的类
         创建测试方法:public方法;无返回值void;方法名称自定义,建议名:test+你要测试方法的名称;无形参;方法上加@Test,这样的方法可以单独执行,不用使用main方法
        */
        
      1. 使用构造方法注入(含参构造器,无set方法)

        Student stu = new Student(“张三”,22);

        spring调用类的含参构造器,在创建对象的同时,在构造方法中给属性赋值,构造注入使用<constructor-arg>标签

        标签:一个 表示构造方法中的一个形参

        标签属性:

        ​ name:表示构造方法的形参名

        ​ index:表示构造方法的参数的位置,参数从左往右位置是 0,1,2……

        value:构造方法的形参是简单类型的,使用 value

        ​ ref:构造方法的形参是引用类型的,使用 ref

        // id 为 对象名   name 为构造函数形参名
        
        a.使用构造方法的形参名进行注入值(标签中name属性就是构造器中的形参名):只有含参构造器,无set方法
        <bean id="school" class="com.bjpowernode.pojo3.School">
            <!-- School类含参构造器形参分别为 name1, address1。这里要和第一种方法区分开-->
            <constructor-arg name="name1" value="清华大学"></constructor-arg>
            <constructor-arg name="address1" value="海淀区"></constructor-arg>
        </bean>
        
        
        b.使用构造方法参数的下标注入值:只有含参构造器,无set方法
        <bean id="stu" class="com.bjpowernode.pojo3.Student">
            <constructor-arg index="0" value="钱七"></constructor-arg>
            <constructor-arg index="1" value="22"></constructor-arg>
            <constructor-arg index="2" ref="school"></constructor-arg> 
        </bean>
        
        
        c.使用默认的构造方法的参数的顺序注入值:只有含参构造器,无set方法
        <bean id="stuSequence" class="com.bjpowernode.pojo3.Student">
            <constructor-arg value="陈十"></constructor-arg>
            <constructor-arg value="22"></constructor-arg>
            <constructor-arg ref="school"></constructor-arg>
        </bean>
        
    2. 如果javaBean的属性值类型是Conllection中的List,Set,Map和Properties集合时,则spring配置文件中可以使用集合元素。

      <!-- 通过<list>配置多个属性文件 -->
      <property name="locations"> // locations 指定使用文件方式配置属性
      	<list>
          	<value>classpath:cf1.properties</value>  //文件的全路径
              <value>classpath:cf2.properties</value>
          </list>
      </property>
      

      在sm整合中,一般使用以下标签即可

      <context:property-placeholder location="jdbc.properties"></context:property-placeholder>
      
前置依赖(补)

Bean(A,B),A不是B的属性,但B的某些值的初始化又是依赖于A,这种依赖关系称为 前置依赖

因为彼此属性之间没有强连接,无法使用ref进行关联配置,使用 spring提供了 depends-on属性。

depends-on 的值设置为 前置依赖Bean的id(如上例中的A的id)

<bean id="beanB" class="com.zjs.pojo.BeanAclass" depends-on="beanA"></bean>

前置依赖Bean会在本Bean实例化之前被创建,在其之后被销毁(栈)。前置依赖Bean可以有多个,通过分号,空格进行分割

循环依赖

Bean(C,D),C使用构造注入D,D使用构造注入C,称为循环依赖

此时 spring报如下错误

Error creating bean with name ‘beanC’: Request bean is currently in creating:Is there an unresoulvable circular reference?

解决办法:

  • 将其中的一个或两个依赖注入修改成 set注入
命名空间
  • c命名空间:对 构造器注入进行简写
  • p命名空间:对 set注入进行简写

使用该命名空间,需要在 根元素加入相应的命名空间。如下

xmlns:c=“http://www.springframework.org/schema/c”

xmlns:p=“http://www.springframework.org/schema/p”

idea有提示,无需手动导入

<bean id="stu" class="com.zjs.pojo.Student" c:name="张三" c:age="19"></bean>

格式:(p命名空间用法相同)

  • 普通类型:c:构造参数名=“值”
  • 引用类型:c:构造参数名-ref=“值”

5.项目案例(三层架构)
项目2

项目2(无需在pom.xml中进行任何配置,无需添加spring的配置文件)

  • 使用三层架构进行用户的插入操作.(只是一个模拟,了解三层架构)

  • 界面层,业务逻辑层,数据访问层(模拟,目前还没用到mybatis).

  • Spring会接管三层架构中哪些对象的创建?

  • 界面层的对象,业务逻辑层的对象,数据访问层的对象。

  • 非Spring接管下的三层项目构建:

  • 实体类:

    ​ com.bjpowernode.pojo: Users[uid,uname,uage,get,set,toString]

  • 数据访问层(mybatis干活的地方):处理业务逻辑层提交的数据,进行数据库的增删改查

    com.bjpowernode.dao:

    ​ UsersMapper.java(接口)
    UsersMapperImpl.java(实现类) ==》这里并没有使用Mybatis中的xml映射文件,只是模拟

    public interface UsersMapper {
        // 增加用户
        int insert(Users user);
    }
    
    public class UsersMapperImpl implements UsersMapper{
        public int insert(Users user) {
            System.out.println(user.getUname() + "用户添加成功");
            return 1;
        }
    }
    
  • 业务逻辑层:

    业务逻辑层中必定有数据访问层的对象:

    ​ private UsersMapper usersMapper= new UsersMapperImpl(); // 接口指向实现类

    com.bjpowernode.service:

    ​ UsersService.java(接口)

    com.bjpowernode.service.Impl:

    ​ UsersServiceImpl.java(实现类 )

    public interface UsersService {
        // 调用 dao层,增加用户
        int insert(Users user);
    }
    
    public class UsersServiceImpl implements UsersService{
        private UsersMapper usersMapper = new UsersMapperImpl();
        public int insert(Users user) {
            // 这里可以添加更复杂的业务
            return usersMapper.insert(user);
        }
    }
    
  • 界面层(springmvc干活的地方):接收前端提交的数据,交给业务逻辑层进行处理

    界面层接收前端数据,进行请求转发或重定向(servlet),springmvc框架优化了该功能(MVC三层架构:视图层,模型层,控制层)

    界面层必定有业务逻辑层的对象:

    ​ private UsersService usersService = new UsersServiceImpl();

    com.bjpowernode.controller:

    ​ UsersController.java

    public class UsersController {
        private UsersService usersService = new UsersServiceImpl();
        
        // 界面层的功能实现:对外提供访问的功能
        // user 为 前端传过来的数据
        public int insert(Users user){
            return usersService.insert(user);
        }
    }
    
    //测试类
    public class MyTest {
        @Test
        public void testInsertUsers() {
            UsersController usersController = new UsersController();
            int num = usersController.insert(new Users(1, "张三", 18));
            System.out.println(num);
        }
    }
    
项目三:spring接管三层对象的创建(基于xml)

基于注解的spring接管三层对象的创建见第6节最后

在xml文件中编写bean代码,让spring容器自动创建对象,具体实现见项目3(基于项目2)

需要添加spring的依赖 和 spring的配置文件applicationContext.xml

// 数据访问层
public interface UsersMapper {
    // 增加用户
    int insert(Users user);
}

public class UsersMapperImpl implements UsersMapper{
    public int insert(Users user) {
        System.out.println(user.getUname() + "用户添加成功");
        return 1;
    }
}
// 业务逻辑层
public interface UsersService {
    // 调用 dao层,增加用户
    int insert(Users user);
}

public class UsersServiceImpl implements UsersService{
    // 对象交由容器创建,能够实现代码的解耦合
    private UsersMapper usersMapper; // 接口指向实现类(实现类由spring创建)
    // 交给spring去 依赖注入值,必须提供无参构造器,set方法。无含参构造器会默认提供无参构造器
    public void setUsersMapper(UsersMapper usersMapper) {
        this.usersMapper = usersMapper;
    }

    public int insert(Users user) {
        return usersMapper.insert(user);
    }
}
// 界面层
public class UsersController {
    private UsersService usersService;  // 接口指向实现类(实现类由spring创建)

    public void setUsersService(UsersService usersService) {
        this.usersService = usersService;
    }

    // 界面层的功能实现:对外提供访问的功能
    // user 为 前端传过来的数据
    public int insert(Users user){
        return usersService.insert(user);
    }
}
<!-- src/main/resources/applicationContext.xml:交由spring容器创建对象 -->
<!-- 创建数据访问层的对象:接口的实现类对象  -->
<bean id="uMapper" class="com.zjs.dao.UsersMapperImpl"></bean>
<!-- 创建业务逻辑层的对象 -->
<bean id="uService" class="com.zjs.service.UsersServiceImpl">
    <property name="usersMapper" ref="uMapper"></property> <!--setUsersMapper(UsersMapper usersMapper); 接口指向 id为uMapper 的实现类对象-->
</bean> 
<!-- 创建界面层的对象 -->
<bean id="uController" class="com.zjs.controller.UsersController">
    <property name="usersService" ref="uService"></property>
</bean>
@Test
public void testInsertUsers() {
    // 创建对象并启动容器
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 取出对象
    UsersController uController = (UsersController) ac.getBean("uController");
    // 测试功能
    uController.insert(new Users(1, "王五", 19));
}

6.基于注解的IOC(适用于改动比较少)

也称为DI(Dependency Injection),它是IOC的具体实现的技术.

使用注解

上依赖(pom.xml):在加入spring-context同时,已经将spring-aop依赖间接加入了。使用注解必须使用 spring-aop依赖

上配置: 基于注解的IOC,必须要在Spring的核心配置文件(applicationContext.xml)中添加包扫描.

<!-- 组件扫描器,组件就是java对象(作用是:说明注解在你的项目中的位置,从而创建对象)
	 base-package:指定注解在你项目中的包名
	 component-scan工作方式:容器启动后,spring会扫描遍历 base-package指定的包,
						 把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值
-->
// 之前都是在此处使用bean标签,提供信息给spring创建对象、赋值
   如今只需要一个 组件扫描器,由它扫描指定的包,找到注解并创建对象、赋值
<context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>

药: 创建对象并依赖注入

汤: xml ==》注解annotation

  1. 创建对象的注解(放在类名上面)

    多注解利于项目分层,使用语法和@Component一样,但有额外的功能:@Repository @Service @Controller

    • @Component:

      • 可以创建任意对象,等同于标签的功能。创建的对象的默认名称是类名的驼峰命名法。
      • 也可以指定对象的名称: @Component(value=“指定名称”)。指定名称 等同于就 xml文件中bean标签的id属性值,其中value可以省略,名称是唯一的(创建的对象在整个spring容器中就一个)
    • @Controller:专门用来创建控制器的对象(Servlet),这种对象可以接收用户的请求,可以返回处理结果 给客户端.

    • @Service:专门用来创建业务逻辑层的对象,负责向下访问数据访问层,处理完毕后的结果返回给界面 层.(业务处理,可以有事务等功能)

    • @Repository:专门用来创建数据访问层的对象(放在DAO的实现类上面),负责数据库中的增删改查所有操作.

      案例: 以上4个注解都符合 以下分析
      // @Component("stu")等同于 <bean id="stu" class="com.zjs.s01.Student" />
      // @Component 等同于 <bean id="student" class="com.zjs.s01.Student" /> id默认为类名的驼峰命名法。也就是 spring创建了如下对象:Student student = new Stundent();
      @Component("stu")  //交给Spring去创建对象,就是在容器启动时创建。spring将创建的对象放到Map集合中(FactoryBeanRegistrySupport类),这个同 第3节概念,语法小节
      public class Student {
          @Value("张三") 	 ===>简单类型的值注入
          private String name;
          @Value("22")
          private int age;
          ...// 可提供也可不提供。提供调用子类的,不提供调用容器的
      }
      
  2. 依赖注入的注解(依赖注入就是赋值,不过这个注入值的操作由容器完成)

    1. 简单类型(8种基本类型+String)的注入,放在属性定义的上面:

      ​ @Value:用来给简单类型注入值(无需提供set方法;有一个value属性,是String类型的,用于注入值。可以省略)

    2. 引用类型的注入:

      spring中通过注解给引用类型赋值,使用的是自动注入原理(见 笔记第4节),支持byName,byType

      byName(按名称注入): java类中引用类型的属性名和spring配置文件中bean中id名称一样且数据 类型一致,这样容器中的bean,spring能够赋值给引用类型
      byType(按类型注入): java类中引用类型的数据类型和spring配置文件中bean的class属性是同 源关系,这样的bean能够赋值给引用类型

      1. @Autowired:使用类型注入值(使用的是byType自动注入),从整个Bean工厂中搜索同源类型的对象进行注入。同源类型也可注入.

        什么是同源类型:

        被注入的类型:简单理解为 类中的类成员变量的类型

        spring容器注入的类型:容器为类中类成员注入值,该值的类型

        • 被注入的类型与spring容器注入的类型是完全相同的类型(见项目中s02相关代码)

          java类中引用类型属性的数据类型 和 bean的class的值 是一样的

        • 被注入的类型(父)与spring容器注入的类型(子)是父子类(见项目中s03相关代码)

          java类中引用类型属性的数据类型(父) 和 bean的class的值(子) 是父子关系的

          注意:

          在有父子类的情况下,若使用按类型注入,就意味着有多个可注入的对象(同源类型均可注入),默认选择父类进行注入。

          此时按照名称进行二次筛选,选中与被注入对象相同名称的对象进行注入

          • 项目s03中的Student类中School类成员变量就是按类型注入
          • 多个可注入的对象:指的是School类和它的子类
          • 按名称进行筛选:若给父子类对象指定了名称,则会从父子类中选择名称与Student类中School类成员变量名相同 的 父类/子类 进行注入
        • 被注入的类型(接口)与spring容器注入的类型(实现类)是接口和实现类的类型

          java类中引用类型属性的数据类型(接口) 和 bean的class的值(实现类) 是接口和实现类关系的

          使用按类型注入(byType),若有多个实现类对象可被注入,则会报错;需要采用按名称注入

          见 项目5 (项目5是在项目2上改造的,见第5节项目3也可)

      2. @Autowired
        @Qualifier(“名称”):使用名称注入值,从整个Bean工厂中搜索相同名称的对象进行注入。此处名称即bean标签中的id(byName)

        当然,用 JDK中的注解@Resource也可,可以减少spring的耦合(@Resource 的功能 = 使用名称注入值)

      @Resource是来自JDK1.8中的注解,可以为引用类型赋值,使用的是自动注入原理,支持byName,byType。默认是byName(先使用byName自动注入,如果赋值失败,再使用byType)

      设置@Resource为只使用byName的方式,需要添加一个属性 name,name的值是bean的id(名称)

​ 注意:如果有父子类的情况下,直接按名称进行注入值.

项目4

项目4

  • 项目构建

    • 添加目录

    • 添加依赖(pom.xml)

      • spring的依赖
      • 添加资源文件指定(build标签,详见mybatis)
    • 相关代码

      s01
      // com.zjs.s01.Student 实体类
      /**
       * @Component
       * 交给spring去创建对象,在容器启动时创建,必须在配置文件中添加包扫描
       * 创建的对象的默认名称是类名的驼峰命名法,也可指定名称:@Component("指定名称"),
         用 ac取值的时候用默认名称或指定名称
       */
      @Component
      public class Student {
          @Value("张三") ===>简单类型的值注入(无需提供set方法)
          private String name;
          @Value("18")
          private int age;
      
          @Override
          public String toString() {
              return "Student{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      '}';
          }
      
          public Student() {  // 可省略,这里只是为了说明spring调用了无参构造器为我们创建了对象
              System.out.println("学生对象的无参构造方法......");
          }
      }
      
      
      // s01/applicationContext.xml
      <!-- 使用注解一定要:添加包扫描 -->
      <context:component-scan base-package="com.zjs.s01"></context:component-scan>
      
          
      // test
      @Test
      public void testStudent() {
          //  启动容器,此时对象便已创建完成
          ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
          // 取出对象,使用  @Component创建的对象的默认名称是类名的驼峰命名法
          Student stu = (Student) ac.getBean("student");
          // 若 使用 @Component("stu"),则需 Student stu = (Student) ac.getBean("stu");才能取出对象,注解里面的值 相当于 bean标签中的id值
          System.out.println(stu);
      }
      
      s02
      // 实体类
      @Component
      public class School {
          @Value("清华大学")
          private String name;
          @Value("海定区")
          private String address;
      
          @Override
          public String toString() {
              return "School{" +
                      "name='" + name + '\'' +
                      ", address='" + address + '\'' +
                      '}';
          }
      
          public School() {  // 可省略,这里只是为了说明spring调用了无参构造器为我们创建了对象
              System.out.println("School的无参构造方法。。。。。。");
          }
      }
      
      @Component
      public class Student {
          @Value("王五")
          private String name;
          @Value("16")
          private int age;
          /**
          第4节 中有较为简洁的讲解
          
          1. 引用类型按类型注入。从整个Bean工厂(注解 or xml)中搜索School类型的同源类型的对象进行注        入,注入的对象默认使用类名的驼峰命名法,该例中是 school。也可以指定对象名(无需提供set方	   法)===> byType
          
             @Autowired
             	  required属性:boolean类型,默认为true -》表示引用类型赋值失败,程序报错,并终止执行
             	  required = false:引用类型如果赋值失败,程序正常执行,引用类型赋值是null
             	  推荐使用 true
          */ 
      
          // 2. 引用类型按名称注入:将名称为 school的对象(通过注解创建的) 赋给Student类的 school属性[类中引用类型属性名 和 bean中id值 相同]  ===> byName
          // 等同于 @Resource(name="school")  这里的name不能省略
              // School类中使用@Component注解,School对象由容器创建,默认使用类名的驼峰命名法,即school。如果School类中用 @Component("schoolNew")为容器所创建的对象指定了名称, @Qualifier里的 school 也要换成 schoolNew
          @Autowired
          @Qualifier("school")
          private School school;
      
          @Override
          public String toString() {
              return "Student{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      ", school=" + school +
                      '}';
          }
      }
          
      // s02/applicationContext.xml
      <!-- 添加包扫描 -->
      <context:component-scan base-package="com.zjs.s02"></context:component-scan>
          
      // 测试类
      @Test
      public void testStudent() {
              ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
              // 取出student对象
              Student student = (Student) ac.getBean("student");
              System.out.println(student);
          }
      
      s03
      @Component
      public class SubSchool extends School {
          @Value("清华附小")
          private String name;
          @Value("海淀小区")
          private String address;
      
          public SubSchool() {  // 可不提供
              System.out.println("这是SubSchool的构造方法。。。。。。");
          }
      
          @Override
          public String toString() {
              return "SubSchool{" +
                      "name='" + name + '\'' +
                      ", address='" + address + '\'' +
                      '}';
          }
      }    
      
      // s03/applicationContext.xml
      <!-- 添加包扫描 -->
      <context:component-scan base-package="com.zjs.s03"></context:component-scan>
      
      // 测试类:
      /**
      1.Student类中的 School变量 按 类型注入:@Autowired:
      		1. 有父子类,使用默认名称 的情况下,注入给student的对象会有多个,但会默认注入父类	
      		2. 若不使用默认名称:
      		   	给父类 @Component("schoolNew"), 子类 @Component("school"),则会注入子类
      		   	这是由于 Student类中 School类成员变量名为 school,若Student类中 School类成员			变量名改为schoolNew,则又会注入父类【底层采用了按名称注入】
      		   	
      2.Student类中的 School变量 按 名称注入:
      	若给父类 @Component("schoolFu"), 子类 @Component("schoolZi"),
      	Student类中School变量按 名称注入 @Autowired 
      						  		  @Qualifier("指定名称")
      	若指定名称为schoolFu,则注入父类对象;若指定名称为schoolZi,则注入子类对象。这时候又跟Student类中School变量的变量名没有关系(前提是保证给父子类指定的对象名 与Student类中School变量的变量名不同)
      
      创建子类对象的同时,会调用父类无参的构造方法		   
      */
      @Test
      public void testStudent() {
              ApplicationContext ac = new ClassPathXmlApplicationContext("s03/applicationContext.xml");
              Student student = (Student) ac.getBean("student");
              System.out.println(student);
          }
      

    补充内容:

    采用上述方法,有一个弊端就是,这些由容器生成的对象的赋值更改起来很麻烦,要一个个的去改。所以有以下改进:

    1. 在resources目录下创建属性配置文件(普通文件)

      // resources\test.properties
      myname=张三
      myage=18
      

      中文乱码:改为utf-8 并进行第2步

      image-20240203090822143

      若依然乱码,则需要在设置编码之后,重新编写 属性配置文件

    2. 在 applicationContext.xml中加载属性配置文件

      <context:property-placeholder location="classpath:test.properties" file-encoding="utf-8"/>
      
    3. 通过${} 的方式使用配置文件中的值,例如:

      @Component
      public class Student {
          // @Value("张三")
          @Value("${myname}")  ==》这个myname中的值是从test.properties文件中读取到的,下面同理
          private String name;
          @Value("${myage}")
          private int age;
      }
      

      通过这种方式,只需要集中修改配置文件中的信息,而不需要在代码中查找

项目5(三层)

项目5 :(项目结构见第5节项目2)

spring接管三层对象的创建(基于注解):===》基于xml见第5节项目3

需要添加spring的依赖 和 spring的配置文件applicationContext.xml)

// 数据访问层
public interface UsersMapper {
    // 增加用户
    int insert(Users user);
}

@Repository  // 交给spring框架去创建数据访问层的对象,该节前面有记
public class UsersMapperImpl implements UsersMapper{
    public int insert(Users user) {
        System.out.println(user.getUname() + "用户添加成功");
        return 1;
    }
}
// 业务逻辑层
public interface UsersService {
    // 调用 dao层,增加用户
    int insert(Users user);
}

@Service   // 交给spring框架去创建业务逻辑层的对象,该节前面有记
public class UsersServiceImpl implements UsersService{
    @Autowired
    private UsersMapper usersMapper; // 接口指向实现类(实现类由spring创建,通过 @Repository)
    
    // 交给spring去依赖注入值,必须提供无参构造器,set方法。会默认提供无参构造器
    public void setUsersMapper(UsersMapper usersMapper) {
        this.usersMapper = usersMapper;
    }

    public int insert(Users user) {
        return usersMapper.insert(user);
    }
}
// 界面层 
@Controller  // 交给spring框架去界面层的对象,该节前面有记
public class UsersController {
    @Autowired
    private UsersService usersService;  // 接口指向实现类(实现类由spring创建)

    public void setUsersService(UsersService usersService) {
        this.usersService = usersService;
    }

    // 界面层的功能实现:对外提供访问的功能
    // user 为 前端传过来的数据
    public int insert(Users user){
        return usersService.insert(user);
    }
}
<!-- src/main/resources/applicationContext.xml:交由spring容器创建对象 -->
<!-- 基于注解的开发,必须包扫描:推荐使用第 7 节第1点 -->
<context:component-scan base-package="com.zjs"></context:component-scan>
// 测试类
@Test
public void testInserUsers() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UsersController usersController = (UsersController) ac.getBean("usersController");
        int num = usersController.insert(new Users(19, "张节", 20));
        System.out.println(num);
    }

Bean详解(补)

什么是bean?

  • 在spring中,由spring IOC容器管理的对象叫做Bean。
  • Bean是由Spring IOC容器实例化,组装和以其他方式管理的对象
  • Bean以及它们之间的依赖关系通过容器配置元数据(xml,java注解 or java代码)来反映
Bean的作用域

在spring配置文件中可以使用 scope 来声明对象的作用域或存活时间。

  • 即 spring 容器 在对象进入其相应的 scope 之前,创建并配置这些对象
  • 在该对象不在处于这些 scope 的限定之后,spring容器通常会销毁这些对象

比如:

<bean id="stu" class="com.zjs.pojo.Student" scope="prototype"></bean>
spring5中的 六种作用域
  1. singleton 单例作用域(默认)

    • 从容器启动或第一次实例化开始,只要容器没有退出或销毁,该类型的单一实例就会一直存活

    • spring可以同一个类进行多个单例Bean的配置,也就是一个类可以对应到多个不同id的对象

  2. prototype 原型作用域

    • 原型作用域的Bean在使用容器的 getBean()方法获取的时候,每次得到的都是一个新的对象。
    • 作为依赖对象注入到其他Bean的时候,也会产生一个新的类对象
    • 始终以懒加载的机制的Bean进行实例化。懒加载的配置对其无效
  3. request 请求作用域

    • 针对每次HTTP请求,spring都会创建一个Bean实例
  4. session 会话作用域

    • 使用于Http Session,同一个 session 共享同一个 Bean实例
  5. application 应用作用域

    • 整个web应用,也就是在 ServletContext 生命周期中使用一个bean实例
  6. websocke

    • html5新特性。在一个websocket连接的生命周期中共用一个bean实例
Bean的自动装配
  • 自动注入,见项目1

Bean的基于 Annotation 的装配

  • 自动注入的注解版本,见项目4
懒加载Bean

默认情况下,spring容器在启动时会将所有的单例Bean实例化。

  • 好处:如果配置有问题,实例化出错在容器启动的时候就可以提前发现
  • 劣势:一次需要实例化的Bean太多,会延缓服务启动的速度,影响系统性能

懒加载Bean

  • 对于一些不需要再容器启动的时候就创建的对象,可以推迟到需要用的时候才去加载和创建

  • 可以全局设置和个别设置

  • 全局设置(少用)

    • 设置根标签的属性 default-lazy-init 为 true

      <beans default-lazy-init="true"></bean>
      
  • 个别设置

    • 设置bean标签的属性 lazy-init 为 true

      <bean id="stu" class="com.bjpowernode.pojo.Student" lazy-init="true"></bean>
      
Bean的生命周期(老杜)
  1. BeanFactoryPostProcessor(Bean的定义处理)
  2. 构造函数实例化Bean
  3. setter注入
  4. Aware相关接口的实现
  5. BeanPostProcessor 前置处理
  6. 判断 是否继承 InitiallizingBean接口 并 覆写方法
  7. 判断 是否配置自定义的 init-method
  8. BeanPostProcessor 后置处理
  9. Bean存活并使用
  10. 容器销毁
  11. 是否继承DisposableBean接口并覆写方法
  12. 判断 是否配置自定义的 destory-method

7.三种添加包扫描的方式

基于注解的开发,必须包扫描

  1. 单个包扫描(推荐使用)

    <!-- 使用多次组件扫描器,指定不同的包 -->
    <context:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
    <context:component-scan base-package="com.bjpowernode.service.impl"></context:component-scan>
    <context:component-scan base-package="com.bjpowernode.dao"></context:component-scan>
    
  2. 多个包扫描,多个包之间以逗号或空格或分号分隔

    <!-- 使用分隔符(;或,)分隔多个包名 -->
    <context:component-scan base-package="com.bjpowernode.controller, com.bjpowernode.service, com.bjpowernode.dao"></context:component-scan>
    
  3. 扫描根包(不推荐,会降低容器启动的速度,导致多做无用功.)

    <!-- 指定父包 -->
    <context:component-scan base-package="com.bjpowernode"></context:component-scan>
    

8.为应用指定多个 Spring 配置文件

当项目越来越大,需要多人合作开发,一个配置就存在很大隐患.

多个配置优势:

  • 每个文件的大小比一个文件要小很多,效率高
  • 避免多人竞争带来的冲突

如果项目中有多个模块(相关的功能在一起),一个模块一个配置文件。

如学生成绩管理系统中,学生考勤模块一个配置文件,学生成绩模块一个配置文件

多文件的分配方式:

  • 按功能模块,一个模块一个配置文件
  • 按类的功能,数据库相关的配置一个配置文件,做事务的功能一个配置文件,做service功能的一个配置文件等

拆分配置文件的策略

  1. 按层拆

    applicationContext_controller.xml        
    	<bean id="uController" class="com.bjpowernode.controller.UsersController">
        <bean id="bController" class="com.bjpowernode.controller.BookController">
    applicationContext_service.xml        
        <bean id="uService" class="com.bjpowernode.controller.UsersService">
        <bean id="bService" class="com.bjpowernode.controller.BookService">
    applicationContext_mapper.xml        
        <bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
        <bean id="bMapper" class="com.bjpowernode.controller.BookMapper">
    
  2. 按功能拆

    applicationContext_users.xml        
        <bean id="uController" class="com.bjpowernode.controller.UsersController">
        <bean id="uService" class="com.bjpowernode.controller.UsersService">
        <bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
    applicationContext_book.xml      
        <bean id="bController" class="com.bjpowernode.controller.BookController">
        <bean id="bService" class="com.bjpowernode.controller.BookService">
        <bean id="bMapper" class="com.bjpowernode.controller.BookMapper">
    

注意:当按以上两种方法进行拆分后,通常需要配合第9节的内容一同使用(可拿项目3以及项目5练手)

项目3的分层:

// 以下4个xml文件都是在resource目录下创建的
applicationContext_mapper.xml
    <!-- 创建数据访问层的对象 -->
    <bean id="uMapper" class="com.zjs.dao.UsersMapperImpl"></bean>
applicationContext_service.xml
    <!-- 创建业务逻辑层的对象 -->
    <bean id="uService" class="com.zjs.service.Impl.UsersServiceImpl">
        <property name="usersMapper" ref="uMapper"></property>
    </bean>
applicationContext_controller.xml
   <!-- 创建界面层的对象 -->
    <bean id="uController" class="com.zjs.controller.UsersController">
        <property name="usersService" ref="uService"></property>
    </bean>
total.xml(主配置文件:用来包含其他的配置文件的。主配置文件一般是不定义对象的)
    <!-- 导入文件:见第9节 -->
    <import resource="applicationContext_mapper.xml"></import>
    <import resource="applicationContext_service.xml"></import>
    <import resource="applicationContext_controller.xml"></import>

经过以上更改后,创建容器对象并启动时,ApplicationContext ac = new ClassPathXmlApplicationContext(“total.xml”);

@Test
public void testInsertUsers() {
    // 创建对象并启动,加载的是主配置文件
    ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml");
    // 取出对象
    UsersController uController = (UsersController) ac.getBean("uController");
    // 测试功能
    uController.insert(new Users(1, "王五", 19));
}

项目5的分层跟整合与以上描述大相径庭,不在赘述

9.spring配置文件的整合

语法:

关键字:"classpath:"表示类路径(class文件所在的目录:target/classes)

在spring的配置文件中要指定其他文件的位置,需要使用classpath,告诉spring到哪去加载读取文件

规范写法: resource=“classpath: “加” resource目录下xml文件的路径(不含resource目录)”

当然,如果主配置文件和其他配置文件在同一包下,“classpath:包名” 可以省略(本教程采用的就是这种)

1)单个文件导入

<import resource="applicationContext_mapper.xml"></import>
<import resource="applicationContext_service.xml"></import>
<import resource="applicationContext_controller.xml"></import>

2)批量导入:使用通配符(切入点表达式中有介绍)

注:主配置文件名称不能包含在通配符的范围内(不能叫做applicationContext_total.xml)

​ 使用这种方案,通配符指定的所有配置文件需要放在一个目录中。通配符对一个目录下的所有文件生效

<import resource="applicationContext_*.xml"></import>

至此,spring有关IOC的内容全部结束


10.面向切面编程AOP

动态代理:

  • 可以在程序的执行过程中,创建代理对象。通过代理对象执行方法,给目标类的方法增加额外的功能(功能增强)。实现业务方法 和 非业务方法(如:日志,事务)的解耦合

  • 实现方式

    • jdk动态代理,使用jdk中的Proxy,Method,InvocationHanderl创建代理对象。jdk动态代理要求目标类必须实现接口 ===》第11节第5小节
    • cglib动态代理,第三方的工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能是final的
  • 动态代理的作用(aop的作用)

    • 在目标类源代码不改变的情况下,增加功能(非业务逻辑的代码【切面类】 的织入 通过动态代理的方式实现,在不改变目标类的源码的前提下实现了功能增强)

    • 减少代码的重复(理由同上)

    • 专注业务逻辑代码

    • 解耦合,让你的业务功能和日志,事务非事务功能分离

AOP(Aspect Orient Programming):面向切面编程,基于动态代理的,可以使用jdk,cglib两种代理方式

AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。在目标类对象不修改源代码的情况下,增加其功能

OOP:面向对象编程

  • 切面:公共的,通用的,重复的功能称为切面(切面一般都是非业务方法,独立使用的,给目标类增强功能的)。比如事务,日志,权限验证

面向切面编程就是将切面提取出来,单独开发,在需要调用的方法中通过动态代理的方式进行织入.

切面的三要素:

  1. 切面的功能:干什么 ===》切面类(@Aspect)实现切面功能
  2. 切面的执行位置: Pointcut表示切面执行的位置 ==> 切入点表达式
  3. 切面的执行时间:使用Advice表示时间,在目标方法之前,还是目标方法之后 ==》 通知
11.手写AOP框架(项目6)

为了方便理解所写的简易版AOP框架。spring实现了AOP,具体使用见下节

项目6

业务:图书购买业务

切面:事务

  1. 第一个版本:业务和切面紧耦合在一起,没有拆分.

    /**
     *  图书购买业务和事务切面耦合在一起
     */
    public class BookServiceImpl {
    
        public void buy(){
    
            try {
                System.out.println("事务开启.......");
                System.out.println("图书购买业务功能实现...........");
                System.out.println("事务提交.......");
            } catch (Exception e) {
                System.out.println("事务回滚.......");
            }
        }
    }
    
  2. 第二个版本:使用子类代理的方式拆分业务和切面.

    /**
     *  使用子类代理的方式进行图书业务和事务切面的拆分(动态代理笔记在mybatis文件夹)
     */
    public class BookServiceImpl {
        //在父类中只有干干净净的业务
        public void buy(){
            System.out.println("图书购买功能实现........");
        }
    }
    
    /**
     *  子类就是代理类,将父类的图书购买功能添加事务切面
     */
    public class SubBookServiceImpl extends BookServiceImpl {
        @Override
        public void buy() {
            try {
                //事务切面
                System.out.println("事务开启.........");
                //主业务实现
                super.buy();
                //事务切面
                System.out.println("事务提交.........");
            } catch (Exception e) {
                System.out.println("事务回滚.........");
            }
        }
    }
    
    // test
    public class MyTest02 {
        @Test
        public void test02(){
            BookServiceImpl service = new SubBookServiceImpl();
            service.buy();
        }
    }
    
  3. 第三个版本:使用静态代理(见mybatis笔记)拆分业务和切面。业务和业务接口已拆分。此时切面紧耦合在业务中.

    public interface Service {
        //规定业务功能
         void buy();
    }
    
    /**
     *  目标对象:业务功能的具体实现
     */
    public class BookServiceImpl implements Service {
        @Override
        public void buy() {
            System.out.println("图书购买业务功能实现............");
        }
    }
    public class ProductServiceImpl implements Service {
        @Override
        public void buy() {
            System.out.println("商品购买业务实现.........");
        }
    }
    
    /**
     *  静态代理已经实现了目标对象的灵活切换
     *  图书购买业务,商品购买业务
     */
    public class Agent implements Service {
    
        //设计成员变量的类型为接口,为了灵活切换目标对象
        public Service target;
    
        //使用构造方法传入目标对象
        public Agent(Service target){
            this.target = target;
        }
        @Override
        public void buy() {
            try {
                //切面功能
                System.out.println("事务开启......");  //日志 权限验证
                //业务功能
                target.buy();
                //切面功能
                System.out.println("事务提交.......");
            } catch (Exception e) {
                System.out.println("事务回滚.......");
            }
        }
    }
    
    public class MyTest03 {
        @Test
        public void test02(){
            Service agent = new Agent(new ProductServiceImpl());
            agent.buy();
        }
    }
    
  4. 第四个版本:使用静态代理拆分业务和业务接口,切面和切面接口.

    image-20231230201041886

    // Service BookServiceImpl ProductServiceImpl类 同第3个,此处不再给出
    
    //切面接口(非业务方法的实现类,类似于jdbc中为简化开发创建的工具类)
    // 通过工具类和动态代理以及反射机制,将 工具类中的 非业务方法 织入到 目标类的业务方法 中,实现了业务方法 和 非业务方法 的解耦合   
    public interface AOP {
        // default关键字:使得实现类没必要实现接口中的所有方法,例如下面的 LogAop
        default void before(){}
        default void after(){}
        default void exception(){}
    }
    
    // 不同的切面
    public class LogAop implements AOP {
        @Override
        public void before() {
            System.out.println("前置日志输出.......");
        }
    }
    
    
    public class TransAop implements AOP {
        @Override
        public void before() {
            System.out.println("事务开启........");
        }
    
        @Override
        public void after() {
            System.out.println("事务提交........");
        }
    
        @Override
        public void exception() {
            System.out.println("事务回滚........");
        }
    }
    
    public class Agent implements Service {
        //传入目标(业务)对象,切面对象
        Service target;
        AOP aop;
    
        //使用构造方法初始化业务对象和切面对象
        public Agent(Service target,AOP aop){
            this.target = target;
            this.aop = aop;
        }
    
        @Override
        public void buy() {
            try {
                //切面
                aop.before();  //事务  日志
                //业务
                target.buy();  //图书  商品
                //切面
                aop.after();   //事务
    
            } catch (Exception e) {
                //切面
                aop.exception();
            }
    
        }
    }
    
    public class MyTest04 {
        @Test
        public void test02(){
            Service agent = new Agent(new ProductServiceImpl(),new TransAop());
            Service agent1 = new Agent(agent,new LogAop());
            agent1.buy();
        }
    }
    
  5. 第五个版本:使用动态代理完成第四个版本的优化.

    // 在4的基础上,修改Agent类为动态代理即可。通过 ProxyFactory得到动态代理对象Agent
    /* 扩展功能更易实现,例如:
    	1.Service 接口添加方法: default String show(int age){return null;} 
    	2.BookServiceImpl 类 实现该方法。 
    这就是动态代理的好处,不管怎么扩充功能,代理类中的代码都不用改变【实现解耦合】。而使用静态代理,Agent类中代码要一直维护。
    
    
    补充:
    	下面的代码有一个问题,当我们执行 buy方法 或 show方法,都会加入切面的功能
    	如何实现在执行 buy方法时 加入切面功能,而执行 show方法时不加入切面功能?==> 通过 method 对象
    	
    	String methodName = method.getName();  // 获取正在执行的方法的名称
    	if("buy".equals(methodName)){
    		 aop.before();  
    		 obj = method.invoke(target, args);  // BookServiceImpl.buy();
    		 aop.after();
    	}else{
    		obj = method.invoke(target, args);  // BookServiceImpl.show();
    	}
    */
    public class ProxyFactory {
        // target 为 Service接口的实现类对象
        public static Object getAgent(Service target, AOP aop){
            //通过 类Proxy 返回生成的动态代理对象
            return Proxy.newProxyInstance(
                //类加载器
                target.getClass().getClassLoader(),
                //目标对象实现的所有的接口
                target.getClass().getInterfaces(),
                //代理功能实现类对象
                new InvocationHandler() {
                    // 通过代理对象Proxy执行方法时,会调用InvocationHandler类中的invoke()
                    @Override
                    public Object invoke(
                        //生成的代理对象
                        Object proxy,
                        //类Method 为正在被调用的目标方法如 buy(),show()
                        Method method,
                        //目标方法的参数
                        Object[] args) throws Throwable {
                        // 目标方法的执行结果
                        Object obj = null;
                        try {
                            // System.out.println(method.getName()); //获取正在被调用的方法名
                            //切面:非业务功能的实现(如事务,日志)==》执行切面类方法(非业务方法)
                            aop.before();  
                            //业务功能的实现:target为目标类对象 ==》执行目标类的方法(业务方法)
                            // 无论后续在目标类中增加多少业务功能,这里总能够通过反射机制获取到目标类                            中所有的业务方法(相比于静态代理,改动的代码少了很多)。在开头也有讲
                            obj = method.invoke(target, args);
                            //切面
                            aop.after();
    
                        } catch (Exception e) {
                            //切面
                            aop.exception();
                        }
                        return obj; //目标方法的返回值
                    }
                }
            );
        }
    }
    
    
    // test
    public class MyTest05 {
        @Test
        public void test02(){
            //得到动态代理对象
            Service agent = (Service) ProxyFactory.getAgent(new BookServiceImpl(),new TransAop());
            agent.buy();
        }
    
        @Test
        public void test03(){
            //得到动态代理对象
            Service agent = (Service) ProxyFactory.getAgent(new BookServiceImpl(),new LogAop());
            System.out.println(agent.getClass());
            String s = agent.show(22);
            System.out.println(s);
        }
    }
    

通过以上代码不难看出,通过 工具类(切面类)和 动态代理,能够实现业务功能(目标类) 和 非业务功能(切面类)的解耦合

为了简化开发,动态代理对象 的实现已经被框架写好了。例如下面讲的AspectJ框架(第17节)。

aop是一个规范,是实现动态代理的一个规范化,一个标准。底层是框架为我们实现了的

aop技术的实现框架:

  	1. spring,主要在事务处理时使用spring内部实现的aop。很少用,比较笨重
  	1. aspectJ,一个开源的,专门做aop的一个框架

我们所需要做的,就是

  1. 编写业务接口,业务实现类,和切面的实现类,

  2. xml文件中进行业务实现类和切面实现类的绑定(底层的实现就是动态代理)==》参见17节项目7

    通过动态代理,将切面类中的 非业务逻辑代码 织入到 实现类中。实现 非业务逻辑代码 和 业务逻辑代码的解耦合。这个功能有框架为我们实现好了,例如下面介绍的AspectJ框架。使得程序员专注于业务逻辑的实现

12.Spring支持的AOP的实现

相关项目

Spring支持AOP的编程(没怎么用,用的是AspectJ),常用的有以下几种:

  1. Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;

  2. After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;

  3. Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;

  4. Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。

    (事务就是一种Around通知)

13.AOP常用的术语
  1. 切面(Aspect):就是那些重复的,公共的,通用的功能称为切面。表示增强的功能,一般是非业务功能。例 如:日志,事务,权限验证,参数检查,统计信息

  2. 连接点(JoinPoint):连接业务方法和切面的位置,连接点其实就是目标类中的某个业务方法。因为在目标方法中要实现(连接)目标方法的功能和切面功能.

  3. 切入点(Pointcut):指定切入的位置,多个连接点构成切入点。切入点可以是一个目标方法,可以是一个类中 的所有方法,可以是某个包下的所有类中的方法.(为多个业务方法 增强同一种功能,这些业务方法的集合称为切入点)

  4. 目标对象:操作谁(给哪个类的方法增加功能),谁就是目标对象.

  5. 通知(Advice):来指定切面方法执行的时机。是在目标方法执行前还是执行后还是出错时,还是环绕目标方法切入切面功能.

切面的三要素:

  1. 切面的功能代码:干什么 ===》切面类(@Aspect注解标识的类都是切面类)
  2. 切面的执行位置: Pointcut表示切面执行的位置 ==> 切入点表达式
  3. 切面的执行时机:使用Advice表示时间,在目标方法之前,还是目标方法之后 ==》 通知

14.什么是AspectJ框架

AspectJ 是一个优秀的面向切面的框架。它扩展了 Java 语言,提供了强大的切面实现(实现了AOP)。它因为是基于java语言开发的,可以无缝扩展。easy to learn and use(易学易用).

使用AOP:目的是给已经存在的一些类和方法,增加额外的功能。前提是不改变目标类中的代码,进行功能增强

使用aspectJ实现aop的步骤:(见18节前项目7)

  1. 新建maven项目

  2. 加入依赖

    1. spring 的依赖
    2. aspectJ 的依赖
  3. 创建目标类:接口和它的实现类(在其中编写 业务方法)

  4. 创建切面类:

    1. 类上面需添加 @Aspect 注解(表明当前类是切面,用于给业务方法增加功能)

    2. 类中定义方法,这些方法就是切面要执行的 非业务逻辑代码。用于给目标类增强功能

      在方法的上面加入 aspectj中的通知注解,例如 @Before;还需要指定切入点表达式 execution()

  5. 创建spring的配置文件:声明对象,把对象交给容器统一管理

    声明对象可以使用注解 或者 xml配置文件

    1. 声明目标类对象

    2. 声明切面类对象

    3. 声明aspectJ框架中的自动代理生成器标签

      自动代理生成器:用来完成代理对象的自动创建功能的【项目6中第5个是自己创建动态代理对象,现在这里让容器自动创建代理对象】用来绑定目标类和切面类,实现功能增强

      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      
  6. 创建测试类,从spring容器中获取目标对象(实际就是代理对象)

    通过代理对象执行方法,实现aop的功能增强

15.AspectJ常见通知类型

回顾第13节:

​ 切面的执行时机:在aop规范中叫做 Advice(通知)

AspectJ 中常用的通知(增强)有四种类型:前面三个用的比较多

  1. 前置通知@Before

  2. 后置通知@AfterReturning

  3. 环绕通知@Around

  4. 最终通知@After

  5. 定义切入点@Pointcut(了解)

  6. @AfterThrowing

16.AspectJ 的切入点表达式(掌握)

表示切面执行的位置

规范的公式:

execution(访问权限 方法返回值 方法声明(参数) 异常类型)

简化后的公式:

execution( 方法返回值 方法声明(参数) )

用到的符号:

* :代表 任意个任意的字符(通配符)
… : 如果出现在方法的参数中,则代表任意参数
如果出现在路径中,则代表本路径及其所有的子路径

+:用在类名后,表示当前类及其子类

​ 用在接口后,表示当前接口及其实现类

示例:(见讲义:红色的是重点)

  • execution(public * *(…)) :任意的公共方法
  • execution(* set*(…)):任何一个以“set”开始的方法
  • execution(* com.xyz.service.impl.*.*(…)):

​ 任意的返回值类型,在com.xyz.service.impl包下的任意类的任意方法的任意参数

  • execution(* com.xyz.service…*.*(…)):

    ​ 任意的返回值类型 ,在com.xyz.service包及其子包下的任意类的任意方法的 任意参数

com.xyz.service.a.b..(…) com.xyz.service..(…)

  • execution(* *…service.*.*(…)):指定所有包下的 service子包下的任意类的任意方法的任意参数

  • execution(* *.service.*.*(…)):指定只有 一级包下的service子包下任意类的任意方法的任意参数 为切入点

17.AspectJ的前置通知@Before
image-20231231142434097

在目标方法执行前切入切面功能。在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名(在切面方法形参中 使用JoinPoint类,项目7中有具体解释).

项目7(切面类的创建)

项目7

实现的步骤:

  1. pom.xml 添加依赖(spring的依赖 和 aspectJ的依赖)

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.5.RELEASE</version> 
    </dependency>
    
    // 资源文件绑定
    <build>
        // 见mybatis笔记第6节
    </build>
    
  2. 代码实现

    三个类都是在com.zjs.s01包下创建的

  3. 创建业务接口(若没有目标类的接口,框架自动使用cglib动态代理)

    public interface SomeService {
        String doSome(String name, int age);
        
        void show();
    }
    
  4. 创建业务实现类

    public class SomeServiceImpl implements SomeService {
        public String doSome(String name, int age) {
            System.out.println("doSome的业务功能实现。。。。");
            return "abcd";
        }
        
        public void show(){
            System.out.println("show的业务功能实现。。。。");
        }
    }
    
  5. 创建切面类,实现切面方法,为目标类增强功能

    //表明当前类是切面类:给业务方法增加功能的类,这个类中有切面的功能代码
    // 必须配置自动代理生成器,该注解才能够被识别,否则会报错
    //交给AspectJ的框架去识别切面类(MyAspect)
    @Aspect	     
    public class MyAspect {
                /**
                 *  所有切面的功能都是由切面方法来实现的
                 *  可以将各种切面都在此类中进行开发
                 *
                 *  前置通知的切面方法的规范:
                 *  1)访问权限是public
                 *  2)方法的返回值是void
                 *  3)方法名称自定义
                 *  4)方法没有参数,如果有也只能是JoinPoint类型(获取目标方法【需进行功能增强的方法】的				*	  签名)。
                 
                 		JoinPoint对象:连接点,即业务方法,表示 要加入切面功能的业务方法。
                 		              目标方法执行时,会将目标方法的信息封装到 JoinPoint对象中
                 		  作用是:可以在通知方法(切面方法)中获取目标方法执行时的信息,例如方法名称,							 方法的实参。
                 		  如果你的切面功能中需要用到目标方法的信息,就在 切面方法中加入形参 JoinPoint
                 		  这个JoinPoint参数的值是由框架赋予的,必须是切面方法中第一个位置的参数
                 
                 *  5)必须使用@Before注解来声明切入的时机是前切功能和切入点
                 *          @Before注解的参数:value  指定切入点表达式,表示切面的功能执行的位置
                 *   业务方法:
                 *          public String doSome(String name, int age)
                 */ 
        
        //这种切入点表达式 直接写死了,执行show方法时无法织入切面方法
        @Before(value = "execution(public String com.zjs.s01.SomeServiceImpl.*(String,int))")
        public void myBefore(){
            System.out.println("切面方法中的前置通知功能实现............");
        }
    
        @Before(value = "execution(public * com.zjs.s01.SomeServiceImpl.*(..))")
        public void myBefore(){
            System.out.println("切面方法中的前置通知功能实现............");
        }
    
        // s01包下任意类的任意方法(推荐使用)
        @Before(value = "execution( * com.zjs.s01.*.*(..))")
        public void myBefore(JoinPoint jp){  // 参数只能是 JoinPoint类型
            System.out.println("切面方法中的前置通知功能实现............");
            System.out.println("目标方法的签名:"+jp.getSignature());
            System.out.println("目标方法的参数值:"+ Arrays.toString(jp.getArgs()));
        }
        
        //s01包及其子包下任意类的任意方法
        @Before(value = "execution( * com.zjs.s01..*(..))")
        public void myBefore(){
            System.out.println("切面方法中的前置通知功能实现............");
        }
    
        // 太开放了,不推荐
        @Before(value = "execution( * *(..))")
        public void myBefore(){
            System.out.println("切面方法中的前置通知功能实现............");
        }
    
    }
    
  6. 在applicationContext.xml文件中进行切面绑定(切面类和业务逻辑层实现类的绑定)(src\main\resources\s01)

    <!-- 创建业务对象 -->
    <bean id="someService" class="com.zjs.s01.SomeServiceImpl"></bean>
    <!-- 创建切面对象-->
    <bean id="myaspect" class="com.zjs.s01.MyAspect"></bean> 
    <!-- 若使用注解,上述两行也可省略,但必须添加包扫描
     					1. SomeServiceImpl类添加 @Service 注解
    					2. MyAspect 类添加 @Component 注解
    3. 在xml文件中添加包扫描
      <context:component-scan base-package="com.zjs.s01"></context:component-scan>
    
    进行如此修改后,test中访问业务对象,默认创建的对象名是 类名的驼峰命名法。创建的业务对象是代理对象
         SomeService someService = (SomeService) ac.getBean("someServiceImpl");
    -->
    
    <!-- 绑定:默认采用jdk动态代理绑定,见18节(再也不用像第11节第5点那样自己创建动态代理对象了)
         自动代理生成器:创建目标对象的代理对象,是在内存中实现的。修改目标对象在内存中的结构,创建为代理       			 对象,所以目标对象就是被修改后的代理对象
    	 aspectj-autoproxy会把spring容器中所有的目标对象(业务对象),一次性都生成代理对象
    	 
    	自动代理生成器通过@Aspect注解扫描切面类,通过 通知和切入点表达式 明确切入的位置和时机,找到目标对                 象,并生成代理对象
     -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  7. 测试类

    @Test
    public void test01() {
        // 启动容器,此时创建对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
        SomeService someService = (SomeService) ac.getBean("someService");
        System.out.println(someService.getClass()); // Proxy类型,jdk动态代理类型
        String s = someService.doSome("张三", 22);
        System.out.println(s);
    }
    
18.AspectJ框架切换JDK动态代理和CGLib动态代理

动态代理的知识见mybatis里面的笔记

  1. jdk动态代理
// applicationContext.xml
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>     ===>默认是JDK动态代理,取时必须使用接口类型接动态代理对象
// test
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
SomeService someService = (SomeService) ac.getBean("someService");

// SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someService"); 类型转换异常,左边是 SomeServiceImpl类型,右边是 Proxy动态代理类型
  1. CGLib动态代理(又称为子类代理,目标类没有接口只能用这种方式实现,spring自动应用cglib)
 applicationContext.xml
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>  ==>设置为CGLib子类代理,可以使用接口或实现类接动态代理对象
// test
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
// 没有SomeService接口
SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someService");
System.out.println(someService.getClass());

记住:使用接口来接动态代理对象,永远不出错.

19.后置通知@AfterReturning

后置通知是在目标方法执行后切入切面功能。可以得到目标方法的返回值,可以根据这个返回值做不同的处理功能

如果目标方法的返回值是简单类型(8种基本类型+String)则不可改变,如果目标方法的返回值是引用类型则可以改变.(值传递和引用传递)

image-20231231162938264

代码实现:四个类都是在com.zjs.s02包下创建的

public class Student{
    private String name;
    private int age;
    // set get 含参、无参构造器,toString
}

public interface SomeService {
    String doSome(String name, int age);
    
    Student change();
}

@Service  // 交由容器创建对象
public class SomeServiceImpl implements SomeService{
    public String doSome(String name, int age) {
        System.out.println("doSome业务方法被执行。。。。");
        // System.out.println(1/0); // 后置通知在目标方法出现异常时不会执行
        return "abcd";
    }
    
    public Student change(){
        System.out.println("change方法被执行。。。。");
        return new Student("张三", 22);
    }
}


@Aspect
@Component
public class MyAspect {
        /**
         * 后置通知的方法的规范:
         * 1)访问权限是public
         * 2)方法没有返回值void
         * 3)方法名称自定义
         * 4)方法有参数(也可以没有参数,如果目标方法没有返回值,则可以写无参的方法。但一般会写有参,这样          *   可以处理无参也可以处理有参)。这个切面方法的参数就是目标方法的返回值:Object
         * 5)使用@AfterReturning注解 表明是后置通知
         *        属性:
         *          value:指定切入点表达式
         *          returning:指定目标方法的返回值的名称。此名称必须与	切面方法的形参名称一致.
         *业务方法:
         *       public String doSome(String name, int age)
         * JoinPoint对象一定要放在形参的第一个位置
         
         后置通知的执行:
         	Object obj = doSome(name, age); // 得到目标方法的返回值
         	myAfterReturning(JoinPoint jp, obj);  // 执行切面方法   
         */
    @AfterReturning(value = "execution(* com.zjs.s02.*.*(..))",returning = "obj")
    public void myAfterReturning(JoinPoint jp, Object obj){  
        //  obj 代表目标方法的返回值。 Object obj = doSome(name, age);
        System.out.println("后置通知功能实现.............." + jp.getSignature());
        
        if(obj != null){
            if(obj instanceof String){
                obj = obj.toString().toUpperCase();
                System.out.println("在切面方法中目标方法的返回值:"+obj);
            }
            if(obj instanceof Student){
                Student stu = (Student) obj;
                stu.setName("李四");
                System.out.println("在切面方法中目标方法的返回值:"+stu);
            }
        }
    }
}
// src\main\resources\s02\applicationContext.xml
<context:component-scan base-package="com.zjs.s02"></context:component-scan>

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Test
public void test01() {  // 基本类型无法通过 后置通知 改变其值
    ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
    SomeService someService = (SomeService) ac.getBean("someServiceImpl");
    String s = someService.doSome("张三", 22);
    System.out.println("测试目标方法中的返回值" + s);
}

@Test
public void test02() {  // 引用类型能通过 后置通知 改变其值
    ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
    SomeService someService = (SomeService) ac.getBean("someServiceImpl");
    Student stu = someService.change();
    System.out.println("测试目标方法中的返回值" + stu);
}
20.环绕通知@Around

它是通过拦截目标方法的方式 ,在目标方法前后都能增强功能的通知。

它是功能最强大的通知,一般在事务中使用此通知。(目标方法之前开启事务,执行目标方法,在目标方法之后提交事务)

它可以轻易的改变目标方法的返回值(比后置通知强,它能改变基本数据类型的值);也能控制目标方法是否被调用执行

环绕通知就等同于jdk动态代理的 InvocationHandler接口

image-20231231163023657

代码实现:3个类都是在com.zjs.s03包下创建的

public interface SomeService {
    String doSome(String name, int age);
}


@Service  // 交由容器创建对象
public class SomeServiceImpl implements SomeService{
    public String doSome(String name, int age) {
        System.out.println("doSome业务方法被执行。。。。" + name);
        return "abcd";
    }
}

@Aspect
@Component
public class MyAspect {
                 /**
                 * 环绕通知方法的规范:
                 * 1)访问权限是public
                 * 2)切面方法有返回值,切面方法的返回值就是目标方法的返回值,可以被修改
                 * 3)方法名称自定义
                 * 4)方法有参数,是固定的ProceedingJoinPoint,作用是执行目标方法。类似于动态代理中			      *	  的 Method类
                 * 5)回避异常Throwable
                 * 6)使用@Around注解声明是环绕通知
                 *      属性:
                 *          value:指定切入点表达式
                 */
    // ProceedingJoinPoint 继承了 JoinPoint,故他也可以拿到目标方法的函数签名
    @Around(value = "execution(* com.bjpowernode.s03.*.*(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        // 获取目标方法的函数签名(控制目标方法是否执行,通过参数)
        Object args[] = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        
        //前切功能实现:在目标方法前增强功能
        System.out.println("环绕通知中的前置功能实现............");
        //目标方法调用:obj即目标方法的返回值 Object obj = doSome();
        Object obj = pjp.proceed(pjp.getArgs()); //等价于 method.invoke();	
        //后切功能实现:在目标方法后增强功能
        System.out.println("环绕通知中的后置功能实现............");
        return obj.toString().toUpperCase();  //改变了目标方法的返回值
    }
}
// src\main\resources\s03\applicationContext.xml
<context:component-scan base-package="com.zjs.s03"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Test
public void test01() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("s03/applicationContext.xml");
    SomeService someService = (SomeService) ac.getBean("someServiceImpl");
    String s = someService.doSome("张三", 22);  //实际执行的是 myAround(); 	
    System.out.println("测试目标方法中的返回值" + s);
}
21.最终通知@After

若目标方法不能正常执行(出现异常),则对于环绕通知而言,后切功能代码无法执行;后置通知无法执行。

而对于最终通知,无论目标方法是否正常执行,最终通知的代码都会被执行.

一般在其中做资源清理工作

代码:

3个类都是在com.zjs.s04包下创建的

// SomeService 类见20节,此处不再给出
@Service  // 交由容器创建对象
public class SomeServiceImpl implements SomeService{
    public String doSome(String name, int age) {
        System.out.println("doSome业务方法被执行。。。。" + name);
        //System.out.println(1/0); //就算出现异常,最终通知依然可以执行
        return "abcd";
    }
}

@Aspect
@Component
public class MyAspect {
     /**
     * 最终通知方法的规范
     * 1)访问权限是public
     * 2)方法没有返回值
     * 3)方法名称自定义
     * 4)方法没有参数,如果有也只能是JoinPoint
     * 5)使用@After注解表明是最终通知
     *   	参数:
     *     		value:指定切入点表达式
     */
    @After(value =  "execution(* com.zjs.s04.*.*(..))")
    public void myAfter(){
        System.out.println("最终通知的功能........");
    }
}
// src\main\resources\s04\applicationContext.xml
<context:component-scan base-package="com.zjs.s04"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Test
public void test01() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("s04/applicationContext.xml");
    SomeService someService = (SomeService) ac.getBean("someServiceImpl");
    String s = someService.doSome("张三", 22);
    System.out.println("测试目标方法中的返回值" + s);
}
异常通知@AfterThrowing
// 切面方法
/*
	执行:
	 	try{
	 		目标方法;
	 	}catch(Exception e){
	 		myAfterThrowing(e);
	 	}
*/
@AfterThrowing(value="execution(* com.zjs.s05.*.*(..))", throwing="ex")
public void myAfterThrowing(Exception ex){
    System.out.println("异常通知,目标方法发生异常时执行:" + ex.getMessage());
}
22.给切入点表达式起别名@Pointcut

如果多个切面切入到同一个切入点,可以使用别名简化开发。该注解不是前面所讲的通知注解

在一个空方法上使用@Pointcut注解,此方法就是切入点表达式的别名,其他通知中的value属性就能用该别名代替切入点表达式。

@Aspect
@Component
public class MyAspect {

    @After(value = "mycut()")
    public void myAfter(){
        System.out.println("最终通知的功能........");
    }
    @Before(value = "mycut()")
    public void myBefore(){
        System.out.println("前置通知的功能........");
    }
    @AfterReturning(value = "mycut()",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知的功能........");
    }
    @Around(value = "mycut()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知中的前置通知的功能........");
        Object obj = pjp.proceed(pjp.getArgs());
        System.out.println("环绕通知中的后置通知的功能........");
        return obj;
    }
    // value 指定切入点表达式
    @Pointcut(value = "execution(* com.bjpowernode.s04.*.*(..))")
    private void mycut(){}  // 这个方法一般不会供外界调用
}

执行顺序:环绕通知的前置通知 ==》前置通知 =》环绕通知的后置通知 ==》最终通知 ==》后置通知

23.SM整合的步骤;spring处理事务

用的技术是ioc。使用spring的ioc技术,将mybatis和spring集成在一起,像一个框架

ioc能让spring为我们创建对象,故可以把mybatis中的对象交给spring统一创建,开发人员从spring容器中获取对象

回顾mybatis使用步骤,对象

  1. 定义 dao接口:StudentMapper

  2. 定义mapper文件:StudetnMapper.xml

  3. 定义mybatis的主配置文件:SqlMapConfig.xml

    必须实现:
        1. 读取 数据库的配置文件
        2. 配置 数据库
        3. 指定mapper文件的位置
    可以不用实现:
        4. 配置别名
        5,打印日志
    
  4. 创建 dao的代理对象:StudentMapper sMapper = SqlSession.getMapper(StudentMaper.class);

要创建的对象有:SqlSessionFactory ===> SqlSession ===> sMapper。这些交给spring创建

我们会使用独立的连接池类替换 mybatis默认自己带的,把连接池类也交给spring创建

SqlSessionFactory的创建要读取主配置文件(所以项目8第7步要配置数据源)

通过以上说明,我们需要让spring创建的mybatis对象有:(项目8 第 7 步)

  1. 独立的连接池类对象,使用阿里的 druid连接池

  2. SqlSessionFactory对象,得到SqlSession对象。

    ​ 使用SqlSessionFactoryBean对象在内部创建SqlSessionFactory

  3. dao代理对象。

    ​ 使用MapperScannConfigure,在这个类的内部调用 getMapper方法,创建接口的dao对象

需要学习的就是上面三个对象的创建语法:使用xml的 bean标签

项目8:SM整合

项目8

  1. 建表 springuser.sql,添加idea中数据库可视化(Mybatis中有讲)

  2. 新建项目,选择quickstart模板

  3. 修改目录,并添加三层目录(pojo,dao(mapper),service,到了springmvc还要添加 controller)

  4. 修改pom.xml文件,添加相关的依赖(使用老师提供 Spring+MyBatis依赖(mysql驱动使用mysql8,要修改)

    spring 的依赖; mybatis的依赖; mysql驱动;spring的事务的依赖;

    mybatis和spring集成的依赖:mybatis官方提供的,用来在spring项目中创建mybatis的 SqlSessionFactory,dao对象的

  5. 添加MyBatis相应的模板(SqlMapConfig.xml和XXXMapper.xml文件)

    添加模板的目的是不在像mybatis笔记中那样 去 粘贴头文件了

    添加模板的步骤:Settings ==> file and code templates ==> 添加模板,将路径中的文件模板添加到idea

    image-20231231212528333

  6. 拷贝jdbc.propertiest属性文件到resources目录下jdbc.properties(老师给的是连接mysql5的,这里我用的是mysql8的,记得改driver和密码。注意两者细微的差别(jdbc.propertiest 和 maven导入依赖),这个在 MyBatis笔记中有讲)

  7. 添加SqlMapConfig.xml文件(MyBatis主配置文件)【使用第5步的模板进行修改】

    // resources 目录下(由于spring接管了部分MyBatis的功能,所以此处给出优化后的配置文件)
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    

    mybatis的配置

  8. 添加applicationContext_mapper.xml(重点:这里接管了 mybatis主配置文件中的绝大部分功能,如读取配置文件,配置数据源,指定mapper文件位置,设置别名)

    添加第7,8步的xml文件需选择 XML Configuration File。如果没有该选项,需要刷新一下pom.xml文件

    // resources 目录下
    <!-- 读取属性文件:配置数据源时${}里面要用到该文件中的内容
     		property-placeholder:属性占位符
     -->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>
    
    <!-- 创建数据源:druid连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property> <!-- serUrl() -->
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    
    <!-- 
    	配置mybatis中提供的 SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory。
    	
    	在mybatis中创建 SqlSessionFactory 需要读取 主配置文件。所以这里要配置数据源和主配置文件
     -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 配置数据源:set注入,将druid连接池赋给了dataSource -->
        <property name="dataSource" ref="dataSource"></property>
        <!-- 配置Mybatis的主配置文件,
    			configLocation是Resource类型,读取配置文件
    			 	它的赋值要使用value,指定文件路径,最好加上 classpath:
     	-->
        <property name="configLocation" value="SqlMapperConfig.xml"></property>
        
        <!-- 注册实体类别名:一定有com.zjs.pojo包 -->
        <property name="typeAliasesPackage" value="com.zjs.pojo"></property>
    </bean>
    
    
    <!-- 注册mapper.xml文件:一定有com.zjs.mapper包 
    	MapperScannerConfigurer,这个类内部会调用getMapper(),生成每个dao接口的代理对象
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 
    		若在上面指定了 SqlSessionFactoryBean对象的id,这里还需要添加如下配置:
    		 <property name="SqlSessionFactoryBeanName" value="SqlSessionFactoryBean对象的id"></property>
    	`-->
        
        <!-- 指定包名,包名是dao接口所在的包
     		MapperScannerConfigurer会扫描这个包(可以扫描多个包)中的所有接口,把每个接口都执行一次			getMapper()方法,得到每个接口的dao对象。
    		创建好的dao对象放入到spring的容器中,默认创建的dao对象的名称是接口名称的驼峰命名法
    	-->
        <property name="basePackage" value="com.zjs.mapper"></property>
    </bean>
    

    SM整合就是:利用spring的ioc技术,让spring创建 mybatis框架中需要的对象,主要有 数据源对象(druid连接池),SqlSessionFactoryBean对象,MapperScannerConfigure对象

  9. 添加applicationContext_service.xml

    <!-- 导入applicationContext_mapper.xml文件 -->
    <import resource="applicationContext_mapper.xml"></import>
    
    <!-- SM是基于注解的开发,所以一定要添加包扫描:一定有com.zjs.service.impl包-->
    <context:component-scan base-package="com.zjs.service.impl"></context:component-scan>
    
    <!-- 事务处理:第31节声明式事务 or 第24节注解式事务 -->
    

    spring的配置

    至此配置结束,因为不是web项目,所以不用在web.xml中注册框架(都没那个目录)

  10. 在pojo下添加Users实体类,Accounts实体类

    public class Users {
        private Integer userId;
        private String userName;
        private String userPass;
        // 无参,含参构造器,get,set方法,toString
    }
    
  11. 添加mapper包,添加UsersMapper接口和UsersMapper.xml文件并开发(DAO)

    UsersMapper.xml文件的创建使用第5步的模板进行修改

    public interface UsersMapper {
        int insert(Users user);
    }
    
    <mapper namespace="com.zjs.mapper.UsersMapper">
        <insert id="insert" parameterType="users">
            insert into users values (#{userId}, #{userName}, #{userPass}) 
        </insert>
    </mapper>
    
  12. 添加service包,添加UsersService接口和UsersServiceImpl实现类

    // com.zjs.service
    public interface UsersService {
        // 增加用户
        int insert(Users user);
    }
    
    // com.zjs.service.impl
    @Service  // 交给容器创建对象
    public class UsersServiceImpl implements UsersService {
        // 所有业务逻辑层一定有逻辑访问层对象:以前是要得到sqlSession对象,调用getMapper方法,现在只要一个注解
        @Autowired  // 在Bean工厂找同源类型进行注入
        UsersMapper usersMapper;
        public int insert(Users user) {
            return usersMapper.insert(user);
        }
    }
    

    从下到上进行开发:pojo层 > dao层(mapper)> service层 ==> controller层

  13. 添加测试类进行功能测试

    @Test
    public void testInsert() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");
        // ac.getBeanDefinitionNames(); // 获取spring所创建的所有对象的名称
        // 取出 UsersServiceImpl:接口指向实现类。这里的 usersService 是 Proxy类型(代理)
        UsersService usersService = (UsersService) ac.getBean("usersServiceImpl");
        // SM整合后事务是自动提交的,所以增删改操作不需要手动commit
        int num = usersService.insert(new Users(100, "张三", "123"));
        System.out.println(num);
    }
    
补充:spring的事务处理(理论)
  1. 什么是事务?

    ​ 事务是指一组sql语句的集合。集合中有多条sql语句, 我们希望这些语句能够都成功,否则的话就都失败。这些sql语句的执行是一致的,作为一个整体执行

  2. 什么时候使用事务?

    ​ 当我的操作,涉及到多个表,或者是多个sql语句的insert,update,delete。需要保证这些语句都是成功才能完成我的功能,或者都失败。保证操作是符合要求的

  3. 在java代码中写程序,控制事务,此时事务应该放在哪里呢?

    ​ service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句

  4. 通常使用jdbc访问数据库,还是mybatis访问数据库,怎么处理事务?

    ​ jdbc访问数据库,处理事务: Connection conn; conn.commit(); conn.rollback();

    ​ mybatis访问数据库,处理事务:SqlSession.commit(); SqlSession.rollback();

    ​ hibernate访问数据库,处理事务:Session.commit(); Session.rollback();

  5. 问题4中事务的处理方式,有什么不足?

    1. 不同的数据库访问技术,处理事务的对象,方法不同。
    2. 程序员需要了解不同数据库访问技术使用事务的原理,掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回滚事务,以及处理事务的多种方法

    总结:多种数据库访问技术,有不同的事务处理机制,对象,方法。

  6. 怎么解决不足?

    ​ spring提供了一种处理事务的统一模型,能使用统一的步骤,方式完成多种不同数据库访问技术的事务处理

    使用spring的事务处理机制,可以完成 mybatis 访问数据库的事务处理,也能完成 hibernate访问数据库的事务处理

  7. 处理事务,需要怎么做,做什么?

    ​ spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了

    1. 事务内部提交,回滚事务,使用的是事务管理器对象,代替你手动 commit, rollback

      事务管理器是一个接口和它的众多实现类

      接口:PlatformTransactionManager,定义了事务重要方法,commit,rollback

      实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了

      ​ mybatis访问数据库 ----- spring创建好的实现类是 DataSourceTransactionManager

      ​ hibernate访问数据库 ----- spring创建好的实现类是 HibernateTransactionManager

      怎么使用:

      你需要告诉spring你用的是那种数据库的访问技术,怎么告诉spring?

       							==》声明数据库访问技术对应的事务管理器实现类,在spring的配置文件中使用 bean标签 声明即可。
      

      例如要使用mybatis访问数据库,应该在 xml配置文件中配置

    2. 你的业务方法需要什么样的事务,说明需要的事务的类型

      说明方法需要的业务:

      1. 事务的隔离级别(TransactionDefinition接口中定义):

        • READ_UNCOMMITTED:读未提交。未解决任何并发问题
        • READ_COMMITTED:读已提交。解决脏读,存在不可重复读 与 幻读
        • REPEATABLE_READ:可重复读。解决脏读,不可重复读,存在幻读
        • SERIALIZABLE:可串行化。不存在并发问题(加锁)
        • DEFAULT:采用 DB 默认的事务隔离级别。mysql默认为 可重复读,oracle默认为 读已提交
      2. 事务的超时时间:表示一个方法的最长执行时间,如果方法执行时超过了时间,事务就回滚。单位是秒,整数值,默认是 -1

      3. 事务的传播行为:控制业务方法是不是有事务的,是什么样的事务的

        7个传播行为:表示你的业务方法调用的时候,事务在方法之间是如何使用的(前三个必须掌握)

        • PROPAGATION_REQUIRED

          若是该传播行为,指定方法必须在事务中执行。

          若当前存在事务,就加入到当前事务中;若当前没有事务则创建一个新事务。 spring的默认事务传播行为

          image-20240125072639372

        • PROPAGATION_REQUIRES_NEW

          总是新建一个事务

          若当前存在事务,就将当前事务挂起,直到新事务执行完毕

          image-20240125073613117
        • PROPAGATION_SUPPORTS

          指定方法支持当前事务,若当前没有事务,也可以以非事务方式执行

          image-20240125073408113
        • PROPAGATION_MANDATORY

        • PROPAGATION_NESTED

        • PROPAGATION_NEVER

        • PROPAGATION_NOT_SUPPORTED

    3. 提交事务,回滚事务的时机

      1. 当你的业务方法执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit

      2. 当你的业务方法抛出运行时异常或ERROR,spring执行回滚,调用事务管理器的rollback

        运行时异常的定义:RuntimeException 和它的子类都是运行时异常,例如NullPointException

      3. 当你的业务方法抛出非运行时异常,主要是受查异常时,提交事务

        受查异常:在你写代码中,必须处理的异常,例如 IOException,SQLException

    总结spring的事务

    1. 管理事务的是 事务管理器和它的实现类
    2. spring的事务是一个统一模型
      1. 指定要使用的事务管理器实现类,使用 bean标签。不同的数据库有不同的事务管理器实现类
      2. 指定那些类,哪些方法需要加入事务的功能(一般是 service层要是实现事务)
      3. 指定方法需要的隔离级别,传播行为,超时时间

​ 你需要告诉spring,你的项目中类信息,方法的名称,方法的事务的传播行为

spring框架中提供的事务处理方案
  1. 适合中小项目使用的注解方案(24节)

    spring框架自己用 aop是给业务方法增加事务的功能,使用 @Transactional注解增加事务(第24节)。

    @Transactional注解是spring框架自己的注解,放在public方法的上面,表示当前方法具有事务,可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等。也可以放到类上面(用的比较少,表示该类的所有方法都使用事务)

    • 使用 @Transactional的步骤

      1. 在applicationContext_service.xml中声明事务管理器对象

        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
      2. 在applicationContext_service.xml中开启事务注解驱动,告诉spring框架,我要使用注解的方式管理事务(对应标题:基于注解的事务)

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

        spring使用aop机制,创建 @Transactional所在的类代理对象,给方法加入事务的功能。

        • spring给业务方法加入事务:

          ​ 使用 aop的环绕通知,在你的业务方法执行之前开启事务,在业务方法执行之后提交或回滚事务。

          ​ @Around(“你要增加的事务功能的业务方法名称”)

          ​ Object myAround(){

          ​ 开启事务,spring给你开启

          ​ try{

          ​ buy(1001, 10);

          ​ spring的事务管理.commit();

          ​ }catch(Exception e){

          ​ spring的事务管理.rollback();

          ​ }

          ​ }

      3. 在你的方法的上面加入 @Transactional

  2. 适合大型项目(31节)

    有很多的类,方法,需要大量的配置事务,使用 aspectJ框架功能,在spring的配置文件中声明类,方法需要的事务。这种方式业务方法和事务配置完全分离。【**声明式事务:**第31节有详细过程】

    • 实现步骤:都是在xml配置文件中实现

      1. 使用的是 aspectJ框架,需要加入依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.5.RELEASE</version> 
        </dependency>
        
      2. 在applicationContext_service.xml中声明事务管理器对象

        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
      3. 声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时时长】)

      4. 配置aop:指定哪些类要创建代理

  3. 项目中的所有事务,必须添加到业务逻辑层上

24.基于注解的事务添加步骤

在项目8基础之上补充:

  1. 在applicationContext_service.xml文件中添加事务管理器,以及事务的注解驱动

    <!--声明事务管理器:使用spring的事务处理,声明mybatis的事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--因为事务必须关联数据库处理,所以要配置数据源,项目8中的数据源 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 一定要导入以tx结尾的包
    开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
    ransaction-manage:事务管理器对象的id
     -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    
  2. 在业务逻辑的实现类(AccountsServiceImpl)上添加注解(也可以在使用事务的方法上添加注解)

    @Transactional(propagation = Propagation.REQUIRED)
    

    REQUIRED表示 增删改操作时必须添加的事务传播特性 propagation:传播

代码实现

在项目8基础上测试 事务

准备工作

//1. pojo下添加,Accounts实体类
public class Accounts {
    private Integer aid;
    private String aname;
    private String acontent;
    // 无参,含参构造器,get,set方法,toString
}

// 2.mapper包下:

// AccountsMapper
public interface AccountsMapper {
    // 添加账户的功能
    int save(Accounts account);
}
// AccountsMapper.xml
<mapper namespace="com.zjs.mapper.AccountsMapper">
    <insert id="save" parameterType="accounts">
        insert into accounts values(#{aid}, #{aname}, #{acontent})
    </insert>
</mapper>
    
// 3.service包下:
public interface AccountsService {
    // 增加账号
    int save(Accounts account);
}

// service.impl包下:
@Service
public class AccountsServiceImpl implements AccountsService {
    @Autowired
    AccountsMapper accountsMapper;
    
    @Transactional(propagation = Propagation.REQUIRED,
              //noRollbackForClassName = {"ArithmeticException"}//添加此句后,尽管发生算术异常,也不会撤销数据库中的数据)
    public int save(Accounts account) {
        int num = 0;
        num = accountsMapper.save(account);
        // 手动抛出运行时异常,则插入到数据库中的数据必须撤销,即底层会自动回滚    
        // 如何实现上述功能?==》添加事务,见笔记(添加事务管理器,添加事务的注解驱动,添加注解)
        System.out.println(1/0);  // 也用于测试第30节内容
        System.out.println("增加账户成功!num = " + num);
        return num;
    }
}
//测试类
@Test
public void testAccounts() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");
    AccountsService accountsService = (AccountsService) ac.getBean("accountsServiceImpl");
    System.out.println(accountsService.getClass());
    accountsService.save(new Accounts(202, "李四2", "账户安全2"));
}
25.@Transactional注解参数详解
@Transactional(propagation = Propagation.REQUIRED,  // 事务的传播特性(第29节)
               noRollbackForClassName = {"ArithmeticException"},  // 指定发生什么异常不回滚,使用的是异常的名称
               noRollbackFor = ArithmeticException.class,  // 指定发生什么异常不回滚,使用的是异常的类型
               rollbackForClassName = "",  // 指定发生什么异常必须回滚(如自定义异常)
               rollbackFor = ArithmeticException.class,  // 指定发生什么异常必须回滚
               timeout = -1,  // 连接超时设置,默认值是-1,表示永不超时
               readOnly = false,  // 默认是false,如果是查询操作,必须设置为true.
               isolation = Isolation.DEFAULT  // 使用数据库自已的隔离级别        
              )

rollbackFor:表示发生指定的异常一定回滚

处理逻辑是:

	1. spring框架会首先检测方法抛出的异常是不是在rollbackFor的属性值中。如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。如上述的ArithmeticException
	2. 如果抛出的异常不在rollbackfor列表中,spring会判断异常是不是RuntimeException,如果是一定回滚
26.Spring的两种事务处理方式

在项目8后的补充内容中也有所讲解

  1. 注解式的事务(24节)

    • 使用@Transactional注解完成事务控制
    • 此注解可添加到类上,则对类中所有方法执行事务的设定。
    • 此注解可添加到方法上,只是对此方法执行事务的处理.
  2. 声明式事务(必须掌握:31节)

    • 在配置文件中添加一次,整个项目遵循事务的设定.
27.Spring中事务的五大隔离级别

在项目8后补充的一节也有讲

  1. 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据

  2. 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)

  3. 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读

  4. 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

  5. 使用数据库默认的隔离级别 isolation = Isolation.DEFAULT

    • ​ MySQL:mysql默认的事务处理级别是’REPEATABLE-READ’,也就是可重复读

    • ​ Oracle:oracle数据库支持READ COMMITTED 和 SERIALIZABLE 这两种事务隔离级别。默认系统事务

      ​ 隔离级别是READ COMMITTED,也就是读已提交

28.为什么添加事务管理器

在项目8后补充的一节也有讲

  • JDBC: Connection con.commit(); con.rollback();
  • MyBatis: SqlSession sqlSession.commit(); sqlSession.rollback();
  • Hibernate: Session session.commit(); session.rollback();

事务管理器用来生成相应数据库连接技术的 连接+执行语句的 对象.

如果使用MyBatis框架,必须使用DataSourceTransactionManager类完成处理

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--因为事务必须关联数据库处理,所以要配置数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

项目中的所有事务,必须添加到业务逻辑层上,这点在项目8后的补充内容中有所提及

29.Spring事务的传播特性(见讲义)

项目8后的补充内容也有较为详细的讲解

多个事务之间的合并,互斥等都可以通过设置事务的传播特性来解决.
常用

  • PROPAGATION_REQUIRED:必被包含事务(增删改必用)
  • PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务
  • PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,自己不新开事务
  • PROPAGATION_NEVER:不能运行在事务中,如果包在事务中,抛异常
  • PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境

不常用

  • PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常
  • PROPAGATION_NESTED:嵌套事务
30.测试传播特性改造24节项目8

UserServiceImpl包裹了AccountsServiceImpl

@Service  //交给Spring去创建对象
public class UsersServiceImpl implements UsersService {    
    @Autowired
    UsersMapper usersMapper;
    @Autowired
    AccountsService accountsService;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int insert(Users users) {
        int num = usersMapper.insert(users);
        System.out.println("用户增加成功!num="+num);

        //调用帐户的增加操作,调用的帐户的业务逻辑层的功能。而这里面有运行时异常,会发生回滚,因为两者的事务传播行为都是 required
        num = accountsService.save(new Accounts(300,"王五","帐户好的呢!"));
        return num;
    }
}
//同下图规律,因为 UsersServiceImpl的事务传播行为是 required, AccountsServiceImpl的事务传播行为也是 requird,所以两个类共享事务。当 AccountsServiceImpl中抛出异常后,表users 和 表accounts 都不能够插入数据
image-20240101145226144
31.声明式事务(spring)
image-20240125065855923

Spring非常有名的事务处理方式:声明式事务.

要求项目中的方法命名有规范

  1. 完成增加操作包含 add save insert set

  2. 更新操作包含 update change modify

  3. 删除操作包含 delete drop remove clear

  4. 查询操作包含 select find search get

配置事务切面时可以使用通配符*来匹配所有方法

// src\main\resources\applicationContext_trans.xml,test类中容器启动导入的文件也要是该文件
<!-- 此配置文件与 applicationContext_service.xml的功能一样,只是事务配置不同 -->

<!-- 导入applicationContext_mapper.xml文件 -->
<import resource="applicationContext_mapper.xml"></import>

<!-- 包扫描 -->
<context:component-scan base-package="com.zjs.service.impl"></context:component-scan>

<!-- 添加事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 

// 设置优先级:order越大优先级越高,即 基于注解的事务优先级 高于 声明式事务。这样就屏蔽了下面所进行的全局配置,而使用@Transactional注解中所配置的信息
<tx:annotation-driven order=”100“ transaction-manager="transactionManager"></tx:annotation-driven>

-->

<!--
	配置事务切面:即声明(需要事务的)业务方法它的事务属性(隔离级别,传播行为,超时时机)。不需要事务的方					法不在这里面进行配置即可。实现了事务配置和源码的剥离
        id:自定义名称,表示<tx:advice />标签中中配置的内容
        transaction-manager:事务管理器对象的id。事务管理器对象是上面引入的

-->
<tx:advice id="myadvice" transaction-manager="transactionManager">
    <!-- tx:attributes:配置事务属性 -->
    <tx:attributes>
        <!-- tx:method:给不同的方法配置事务属性,没指定的用默认值
						 name:方法名称,可使用通配符。 *select*表示方法名中带有select的所有方法
 						 propagation:传播行为,枚举值
						 isoation:隔离级别
						 rollback-for:指定的异常类名,全限定类名。发生异常一定回滚
-->
        <tx:method name="*select*" read-only="true"/> //查询操作readonly必须设置为true,见25节。查询操作不需要事务所以不用配置 propagation
        <tx:method name="*find*" read-only="true"/>
        <tx:method name="*search*" read-only="true"/>
        <tx:method name="*get*" read-only="true"/>
        
        <tx:method name="*insert*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
        <tx:method name="*add*" propagation="REQUIRED"/>
        <tx:method name="*save*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
        <tx:method name="*set*" propagation="REQUIRED"/>
        
        <tx:method name="*update*" propagation="REQUIRED"/>
        <tx:method name="*change*" propagation="REQUIRED"/>
        <tx:method name="*modify*" propagation="REQUIRED"/>
        
        <tx:method name="*delete*" propagation="REQUIRED"/>
        <tx:method name="*remove*" propagation="REQUIRED"/>
        <tx:method name="*drop*" propagation="REQUIRED"/>
        <tx:method name="*clear*" propagation="REQUIRED"/>
        <!-- 除以上方法之外的所有方法的事务属性 -->
        <tx:method name="*" propagation="SUPPORTS"/>
    </tx:attributes>
</tx:advice>

<!-- 绑定切面和切入点:-->
<aop:config>
    <!-- 配置切入点表达式(起别名):指定哪些包中的类要使用事务(一般都在service层中使用事务)
 			id:切入点表达式的名称,唯一值
			expression:切入点表达式,指定那些类要使用事务,aspectj会创建代理对象

				表明 所有包的service.impl子包下所有类中所有方法都满足如上事务的配置。
				这里也可以写成 com.zjs.service.impl.*.*(..)
	-->
    <aop:pointcut id="mycut" expression="execution(* *..service.impl.*.*(..))"></aop:pointcut>
    
    <!-- 配置增强器:关联advice和pointcut
		advice-ref:通知,<tx:advice /> 中配置的内容,即其id值
		pointcut-ref:切入点表达式的id
	-->
    <aop:advisor order="1"  advice-ref="myadvice" pointcut-ref="mycut"></aop:advisor>
</aop:config>

<tx:advice />标签显得冗余是因为荣姐把所有的全配置了,在目前做的玩具项目中,不需要进行如此细致的配置。name使用具体的方法名即可,也不用配置那么多

补充:SM + servlet

按照学习顺序:servlet + jsp ===》 mybatis ==》 spring,我们现在就可以通过以上技术创建web项目,到了后期学了springmvc框架,就可以将servlet+jsp替换掉,SSM框架集合完毕

web项目中如何使用容器对象?
  1. 做的是javase项目有main方法的,执行代码是执行main方法的,在main里面创建的容器对象

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    
  2. web项目是在tomcat服务器上运行的,tomcat一启动,项目一直运行的。(web项目的main方法在tomcat中)

需求:

​ web项目中 容器对象ac 只需要创建一次,把容器对象ac 放入到全局作用域 ServletContext中

如何实现:

​ 使用监听器,当全局作用域对象被创建时,创建容器对象,并存入ServletContext

监听器作用:

  1. 创建容器对象:

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    
  2. 把容器对象放入到ServletContext域中:

    ServletContext.setAttribute(key, ac);
    

监听器对象可以自己创建,也可以使用框架中提供好的 ContextLoaderListener

ContextLoaderListener底层实现:

  1. 创建容器对象:

    • 它有一个变量:private WebApplicationContext context;

      context = this.createWebApplicationContext(servletContext);
      
      public interface WebApplicationContext extends ApplicationContext
      

      ApplicationContext:javase项目中使用的容器对象

      WebApplicationContext:web项目中使用的容器对象

  2. 将容器对象放到 ServletContext域中

    • // key: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
      // value: this.context
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
      
      String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
      
项目代码

需求:使用 项目8中的 users表 springuser.sql,完成用户从jsp中提交数据,然后将数据保存到 mysql数据库中 的功能,并且将数据显示到页面(该项目并没有使用到事务)

步骤:

  1. 通过maven创建web项目

  2. 修改目录,将web.xml和index.jsp删除并新建。添加三层目录结构(pojo,mapper,service,controller)

  3. 在pom.xml文件中加入依赖, Spring+MyBatis依赖(mysql驱动使用mysql8,要修改)

    因为使用到 servlet 和 jsp,所以pom.xml文件中还需要加入 servlet 和 jsp的 依赖

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
    </dependency>
    
  4. 参见第23节项目8,进行如下的配置(代码一点没变,赋值粘贴即可)

    1. 拷贝jdbc.properties属性文件(jdbc.properties 改 driver 和 密码)
    2. 添加mybatis 的主配置文件 SqlMapConfig.xml
    3. 添加 applicationContext_mapper.xml,让spring创建 mybtis需要的对象
    4. 添加applicationContext_service.xml,导入mapper.xml,以及包扫描,和事务相关的配置
    1. 创建实体类 Users
    2. 添加mapper包,添加UsersMapper接口和UsersMapper.xml文件并开发(DAO)
    3. 添加service包,添加UsersService接口和UsersServiceImpl实现类
  5. 在index.jsp中进行数据的提交,result.jsp中显示添加成功

    index.jsp:
    <p>注册用户</p>
    <form action="${pageContext.request.contextPath}/reg" method="post">
        <table>
            <tr>
                <td>id</td>
                <td><input type="text" name="id"></td>
            </tr>
            <tr>
                <td>name</td>
                <td><input type="text" name="name"></td>
            </tr>
            <tr>
                <td>password</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="注册用户"></td>
            </tr>
        </table>
    </form>
    
    
    result.jsp:
    <h2>result.jsp注册成功</h2>
    
  6. 在controller包中编写servlet,并在web.xml文件中注册该servlet

    public class RegisterServlet extends HttpServlet {
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            String id = request.getParameter("id");
            String name = request.getParameter("name");
            String password = request.getParameter("password");
    
            // 创建spring的容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");
            System.out.println("容器的信息:" + ac);
    
            UsersService usersServiceImpl = (UsersService) ac.getBean("usersServiceImpl");
    
            Users user = new Users();
            user.setUserId(Integer.parseInt(id));
            user.setUserName(name);
            user.setUserPass(password);
            usersServiceImpl.insert(user);
    
            request.getRequestDispatcher("/result.jsp").forward(request, response);
        }
    }
    
    web.xml
    <servlet>
        <servlet-name>RegisterServlet</servlet-name>
        <servlet-class>com.zjs.controller.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>RegisterServlet</servlet-name>
        <url-pattern>/reg</url-pattern>
    </servlet-mapping>
    
  7. 配置tomcat,运行程序

整个的流程到这基本上已经通了,但是有一个问题:在RegisterServlet中,spring的容器对象ClassPathXmlApplicationContext是我们手动创建的,我们每提交一次表单,就会新建一个 spring的容器对象,这样太浪费资源。spring的容器对象只需要创建一次,把其放到全局作用域 ServletContext中即可

为此,我们需要使用 监听器,让其为我们创建spring 的容器对象【还需要配置过滤器,不然会有中文乱码,可以看springmvc的相关部分】

为了添加监听器,步骤如下:

  1. 添加依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    
  2. 在 web.xml中注册监听器:ContextLoaderListener

    <!-- 
    	注册监听器:
    		配置监听器:目的是创建容器对象,创建了容器对象,就能把 applicationContext.xml配置文件中的				  所有对象都创建好,用户发起请求就可以直接使用对象l
    		监听器被创建对象后,会读取 /WEB-INF/applicationContext.xml配置文件
    		     为什么要读取文件:因为在监听器中要创建 ApplicationContext对象,需要加载配置文件。
    					        /WEB-INF/applicationContext.xml就是监听器默认读取的sping配置文							 件路径
    		     可以修改默认的文件位置:使用 context-param重新指定文件的位置
    
    
    	一个小问题:
             1. 报错: Could not open ServletContext resource [/jdbc.properties]
               出错原因:
                  在applicationContext_mapper.xml中加载配置文件时:
                        <context:property-placeholder location="jdbc.properties"/>
                  要将其改为:
                   <context:property-placeholder location="classpath:jdbc.properties"/>
    
             2. 报错:Could not open ServletContext resource [/SqlMapConfig.xml]
                出错原因:
                  在applicationContext_mapper.xml中配置 SqlSessionFactoryBean时:
                        <property name="configLocation" value="SqlMapConfig.xml"/>
                  要将其改为:
                   <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
    
    原因:ClassPath指定的是java加载类的路径。只有类在ClassPath中,java命令才能找到它,并解释它。
    -->
    <context-param>
        <!-- contextConfigLocation:表示配置文件的路径 -->
        <param-name>contextConfigLocation</param-name>
        <!-- 自定义配置文件的路径:service.xml中导入了 mapper.xml -->
        <param-value>classpath:applicationContext_service.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

修改servlet中的代码,获取ServletContext域中的容器对象:

WebApplicationContext ac = null;

//1. 获取ServletContext中的容器对象,创建好的容器对象,拿来就用
String key = " WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE";
Object  attr = getServletContext().getAttribute(key);
if (attr != null) {
    ac = (WebApplicationContext) attr;
}

//2. 使用框架中的方法,获取容器对象
ServletContext sc = getServletContext();
ac = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);

补充:在web.xml中配置中文乱码过滤器(使用框架中提供好的:CharacterEncodingFilter)

<filter>
    <filter-name>encode</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encode</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping> 
       user.setUserId(Integer.parseInt(id));
       user.setUserName(name);
       user.setUserPass(password);
       usersServiceImpl.insert(user);

       request.getRequestDispatcher("/result.jsp").forward(request, response);
   }

}


```xml
web.xml
<servlet>
    <servlet-name>RegisterServlet</servlet-name>
    <servlet-class>com.zjs.controller.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>RegisterServlet</servlet-name>
    <url-pattern>/reg</url-pattern>
</servlet-mapping>
  1. 配置tomcat,运行程序

整个的流程到这基本上已经通了,但是有一个问题:在RegisterServlet中,spring的容器对象ClassPathXmlApplicationContext是我们手动创建的,我们每提交一次表单,就会新建一个 spring的容器对象,这样太浪费资源。spring的容器对象只需要创建一次,把其放到全局作用域 ServletContext中即可

为此,我们需要使用 监听器,让其为我们创建spring 的容器对象【还需要配置过滤器,不然会有中文乱码,可以看springmvc的相关部分】

为了添加监听器,步骤如下:

  1. 添加依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    
  2. 在 web.xml中注册监听器:ContextLoaderListener

    <!-- 
    	注册监听器:
    		配置监听器:目的是创建容器对象,创建了容器对象,就能把 applicationContext.xml配置文件中的				  所有对象都创建好,用户发起请求就可以直接使用对象l
    		监听器被创建对象后,会读取 /WEB-INF/applicationContext.xml配置文件
    		     为什么要读取文件:因为在监听器中要创建 ApplicationContext对象,需要加载配置文件。
    					        /WEB-INF/applicationContext.xml就是监听器默认读取的sping配置文							 件路径
    		     可以修改默认的文件位置:使用 context-param重新指定文件的位置
    
    
    	一个小问题:
             1. 报错: Could not open ServletContext resource [/jdbc.properties]
               出错原因:
                  在applicationContext_mapper.xml中加载配置文件时:
                        <context:property-placeholder location="jdbc.properties"/>
                  要将其改为:
                   <context:property-placeholder location="classpath:jdbc.properties"/>
    
             2. 报错:Could not open ServletContext resource [/SqlMapConfig.xml]
                出错原因:
                  在applicationContext_mapper.xml中配置 SqlSessionFactoryBean时:
                        <property name="configLocation" value="SqlMapConfig.xml"/>
                  要将其改为:
                   <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
    
    原因:ClassPath指定的是java加载类的路径。只有类在ClassPath中,java命令才能找到它,并解释它。
    -->
    <context-param>
        <!-- contextConfigLocation:表示配置文件的路径 -->
        <param-name>contextConfigLocation</param-name>
        <!-- 自定义配置文件的路径:service.xml中导入了 mapper.xml -->
        <param-value>classpath:applicationContext_service.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

修改servlet中的代码,获取ServletContext域中的容器对象:

WebApplicationContext ac = null;

//1. 获取ServletContext中的容器对象,创建好的容器对象,拿来就用
String key = " WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE";
Object  attr = getServletContext().getAttribute(key);
if (attr != null) {
    ac = (WebApplicationContext) attr;
}

//2. 使用框架中的方法,获取容器对象
ServletContext sc = getServletContext();
ac = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);

补充:在web.xml中配置中文乱码过滤器(使用框架中提供好的:CharacterEncodingFilter)

<filter>
    <filter-name>encode</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encode</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping> 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/441208.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux——进程间通信

目录 进程间通信介绍 什么是进程间通信 为什么要进行进程间通信 怎么做到进程间通信 管道 管道的原理 匿名管道 pipe函数 简单线程池 管道读写的规则 命名管道 创建一个管道文件 在代码中创建管道 在代码中删除管道 命名管道实现serve与client通信 system V共享…

数组连续和 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 给定一个含有N个正整数的数组&#xff0c;求出有多少连续区间&#xff08;包括单个正整数&#xff09;&#xff0c;它们的和大于等于 x。 输入描述 第一行为两个…

掌握Python操作Word:从基础到高级全覆盖

掌握Python操作Word&#xff1a;从基础到高级全覆盖 引言Python操作Word的基础文档的创建与打开文档的基本操作 创建和打开Word文档创建新的Word文档打开现有文档读取文档内容修改现有文档 编辑文档内容添加和编辑文本设置格式插入标题 处理文档结构操作段落列表的处理表格的操…

董宇辉所有商标已转到与辉同行名下!

近日董宇辉此前由东方优选申请的所有商标已转到与辉同行主体名下&#xff0c;普推知产老杨经检索发现&#xff0c;这些商标都是2022年6月由东方优选提交申请&#xff0c;在2023年12月28时提交商标转让&#xff0c;最近转让成功&#xff0c;转让周期是2个半月左右。 转让的商标除…

Windows11下载、安装和配置JDK(包含多个版本的JDK配置)

下载JDK17 下载地址 JDK_DOWNLOAD选择JDK17版本 安装JDK17 双击打开安装包 -> 选择下一步 -> 选择安装路径&#xff08;注意不要安装在带有中文的路径下&#xff09;->修改完路径后点击下一步->安装完成。 检验安装是否成功&#xff0c;打开cmd&#xff0c;输…

C#中实现接口的一些小知识(C#用abstract或virtual来实现接口成员)

文章目录 不可用的修饰可用的修饰非抽象类实现接口抽象类实现接口抽象类与接口方法同名时一同实现 不可用的修饰 在C#中实现接口时&#xff0c;我们不能直接使用static或const来实现接口成员&#xff0c;因为接口中的成员默认都是实例成员&#xff0c;并且它们表示一种契约&am…

每日学习总结20240308

每日总结 20240305 常用控件 QPushButton&#xff08;按钮&#xff09;&#xff1a;用于触发操作或响应用户点击事件。QLabel&#xff08;标签&#xff09;&#xff1a;用于显示文本或图像。QLineEdit&#xff08;行编辑器&#xff09;&#xff1a;单行文本输入框&#xff0…

Python学习笔记-Flask实现简单的抽奖程序(增加图片显示)

1.创建static文件夹,存放图片文件 2.hero列表数据更改为要抽奖的图片名 3.html中可以编写python语句,遍历hero列表内容渲染到表格中 4.在点击随机抽取后,可以获得名称,然后使用img标签,将获取的名称拼接到路径中 3.初始页面,访问127.0.0.1:5000/index 4.点击随机抽取后 5.py…

方阵的特征值与特征向量

目录 特征值 & 特征向量 相关性质 特征值 & 特征向量 相关性质

java(框架) springboot-1 基础使用+mybaits使用

学习视频&#xff1a;b站黑马java教程 tomcat spring-boot工程内嵌了tomcat服务器 所有请求经过DispatcherServlet(实现servlet接口的类)(核心控制器/前端控制器)处理&#xff0c;再通过DispatcherServlet转发给各个controller。 最后通过DispatcherServlet给浏览器响应数据…

3D数字孪生运行不起来?该检查你的电脑配置了

运行3D数字孪生项目通常需要一定的计算资源和图形处理能力。以下是一些常见的电脑配置要求&#xff0c;可以作为参考&#xff1a;1处理器&#xff08;CPU&#xff09;&#xff1a;推荐使用多核心处理器&#xff0c;如Intel Core i7或更高级别的处理器。较高的时钟频率和较大的缓…

RocketMQ的事务消息是如何实现的?

RocketMQ的事务消息是通过 TransactionListener接口来实现的。 在发送事务消息时,首先向RocketMQ Broker 发送一条‘half消息’(半消息),半消息将被存储在broker端的事务消息日志中,但是这个消息还不能被消费者消费。 接下来,在半消息发送成功后,应用程序通过执行本地事务…

msvcr110.dll丢失的5种修复方法,快速修复msvcr110.dll缺失问题

MSVCR110.dll文件的丢失可能会引发一系列的问题与不便&#xff0c;严重影响到用户的计算机使用体验。首先&#xff0c;由于MSVCR110.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;它的缺失可能导致许多基于此运行库编译的应用程序无法正常启动或运行&a…

52. N 皇后 II

52. N 皇后 II 题目-困难难度1. 回溯 题目-困难难度 n 皇后问题 研究的是如何将 n 个皇后放置在 n n 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回 n 皇后问题 不同的解决方案的数量。 示例 1&#xff1a; 输入&#xff1a;n …

蓝桥杯集训·每日一题2024 (二分,双指针)

前言&#xff1a; 开学了&#xff0c;平时学习的压力也逐渐大起来了&#xff0c;不过还算可以接受&#xff0c;等到后面阶段考的时候就不一样了&#xff0c;我目前为了转专业退选了很多课&#xff0c;这些课我都需要花时间来刷绩点&#xff0c;不然保研就没有竞争力了。我自己会…

人工蜂群算法

人工蜂群算法 人工蜂群算法&#xff08;Artificial Bee Colony Optimization,ABC&#xff09;是一种基于蜜蜂觅食行为的优化算法&#xff0c;由土耳其学者Karaboga于2005年提出&#xff0c;算法模拟蜜蜂的采蜜行为对优化问题进行求解。 算法原理 ABC算法的核心思想是将优化问…

STM32基础--构建自己的固件库

CMSIS 标准及库层次关系 因为基于 Cortex 系列芯片采用的内核都是相同的&#xff0c;区别主要为核外的片上外设的差异&#xff0c;这些差异却导致软件在同内核&#xff0c;不同外设的芯片上移植困难。为了解决不同的芯片厂商生产的 Cortex 微控制器软件的兼容性问题&#xff0…

API可视化编排,提高API可复用率

在数字化时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为不同软件应用之间沟通的桥梁。然而&#xff0c;如何高效管理、编排和复用这些API&#xff0c;成为了企业和开发者面临的重要挑战。随着技术的不断进步&#xff0c;RestCloud API可视化编排应运而生&…

PCIE的TLP包的封包解包原理

前言&#xff1a;开始pcie项目之前需要知道&#xff0c;本次项目我们是使用现有的框架RIFFA框架去完成设计的&#xff0c;因此比起具体代码的含义&#xff0c;更注重框架的使用。在开始项目之前需要了解PCIE的组建包过程。 一、TLP包的基本格式&#xff1a; 1.1整体包结构概述…

01-DevOps代码上线-git入门及gitlab远程仓库

一、准备学习环境 10.0.0.71-gitlab 2c2g-20GB 10.0.0.72-jenkins 2c2g-20GB 10.0.0.73-sonarqube 1c1g-20GB 10.0.0.74-nexus 1c1g-20GB 10.0.0.75-dm 1c1g-20GB &#xff08;模拟写代码服务器&#xff09; 在centos系统中&…