Spring学习(IOC、AOP、代理、事务)

认识 Spring 框架

简介
  • Spring:春天------>给软件行业带来了春天!
  • 2002,首次推出了Spring框架的雏形:interface21框架!
  • Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日发布了1.0正式版。
  • Rod Johnson,Spring Framework创始人,著名作者。很难想象Rod Johnson的学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
  • Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
  • SSH:Struct2 + Spring + Hibernate
  • SSM:SpringMVC + Spring + Mybatis
优点
  • Spring是一个开源的免费的框架(容器)
  • Spring是一个轻量级的、非入侵式的框架【降低组件之间耦合度 实现解耦】
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务的处理【基于切面和惯例】,对框架整合的支持

总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

组成

常用术语
  • 框架:能完成一定功能的半成品
  • 非侵入时设计:从框架角度来说不用继承框架提供的任何类 不影响更换
  • 轻量级和重量级:相对来说的,轻量级是非入侵的,依赖的东西少,占用资源少,部署简单等
  • JavaBean:符合 JavaBean 规范的 Java 类
  • POJO:简单老式Java对象
  • 容器:装对象的对象。。。管理对象的生命周期
Java Bean
  • 必须是个公有(public)类
  • 有无参构造函数
  • 用公共方法暴露内部成员属性(getter,setter)

实现这样规范的类,被称为Java Bean。即是一种可重用的组件。

在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。

bean 并不简简单单非要实现JavaBean的规范 其实Spring容器包容性很强 可以处理绝大多数的Java类

  • Spring Boot
    • 是一个快速开发框架,通过用MAVEN依赖的继承方式,帮助我们快速整合第三方常用框架,完全采用注解化(使用注解方式启动SpringMVC),简化XML配置,内置HTTP服务器(Tomcat,Jetty),最终以Java应用程序进行执行。
  • Spring Cloud
    • SpringCloud是基于SpringBoot实现的。
    • 是一套目前完整的微服务框架,它是是一系列框架的有序集合。它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过SpringBoot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署。

IOC和DI

控制反转是一种设计思想,依赖注入是实现这种设计思想的一种办法。

对象由Spring来创建,管理,装配!

IOC 控制反装
理解:

以往的编程中,对象的创建和对象之间的依赖关系全靠我们自己用编码去实现【程序自己创建实现关系的】,而这里的控制反转就是让我们把创建对象和对象之间的依赖关系这种采用硬编码实现的方式变成第三方去实现,包括用 xml 文件配置 和 注解的方式,这样就不用我们为繁琐的创建对象和对象之间关系去做代码层硬编码的实现了。Spring这个容器就用控制反转这个思想去管理对象的创建和生命周期的,实现的方式就是依赖注入。

  • 控制:传将统的程序自己创建对象的方式变成Spring去创建对象
  • 反转:程序本身不去创建对象,而是去接受对象
  • IOC是一种编程思想,由主动的编程变成被动的接收。
DI 依赖注入

一个类是另一个类的一个属性,则这个属性就可以采用 set 方法的方式被注入到这个类中 依赖注入就类似于这种

一个系统中可能会有成千上万个对象。如果要手工维护它们之间的关系,这是不可想象的。我们可以在Spring的XML文件描述它们之间的关系,由Spring自动来注入它们——比如A类的实例需要B类的实例作为参数set进去。

依赖注入就是指用set方法去注入

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

Spring简单配置

配置文件:applicationContext.xml

  • 别名

    <!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
    <alias name="user" alias="userNew"/>
    
  • Bean的配置

<!--
    id:bean的唯一标识符,也就是相当于对象名
    class:bean对象所对应的全限定名:包名+类名
    name:也是别名,而且name可以同时取多个别名
        -->
<bean id="userT" class="com.kuang.pojo.UserT" name="user2 u2,u3;u4">
   <property name="name" value="黑心白莲"/>
</bean>
  • import

    它可以将多个配置文件,导入合并为一个。

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

    <!--
        把数据库的配置信息写在一个独立的文件中,编译修改数据库的配置内容
        让spring知道jdbc.properties文件的位置
    -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 扫包 包括注解的配置 -->
    <context:component-scan base-package="com.gpb" />

    <!--声明数据源DataSource,作用是连接数据库-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">

        <!--set注入提供连接数据库信息-->
        <!--<property name="url" value="jdbc:mysql://localhost:3306/ssm" />-->
        <!--<property name="username" value="root" />-->
        <!--<property name="password" value="123456" />-->
        <!--<property name="maxActive" value="20" />-->

        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${jdbc.maxActive}" />

    </bean>

    <!--SqlSessionFactory-->
    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory-->
    <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <!--set注入,把数据库连接池付给dataSource属性-->
        <property name="dataSource" ref="myDataSource"/>
        <!--mybatis主配置文件的位置
            configLocation属性是Resource类型,读取配置文件
            它的赋值使用的是value , 指定文件的路径,使用的是classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>

    <!--创建 dao对象
        使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer在内部调用getMapper()生成每个dao接口的代理对象
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定的是SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="SqlSessionFactory"/>

        <!--指定包名,包名是dao接口所在的包名
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象
            创建好的dao对象放入到spring的容器中

            dao默认对象的名称:是接口名字的首字母小写
        -->
        <property name="basePackage" value="com.md.dao"/>

        <!--多个包-->
        <!--<property name="basePackage" value="com.md.dao,com.md.dao2"/>-->
    </bean>
    <!--下面的就是自己定义的service-->
    <!--声明service-->
    <bean id="studentService" class="com.md.service.impl.StudentServiceImpl">
        <!--就是上面通过创建的dao对象-->
        <property name="studentDao" ref="studentDao"/>
    </bean>
</beans>
Spring创建Bean的方式
  • 调用构造器创建Bean【默认】

    <!--默认通过无参构造进行创建-->
    <bean id="user" class="com.gpb.pojo.User"/>
    
  • 调用静态工厂方法创建Bean

    <!--通过静态工厂的方式创建,其中factory-method指的就是创建User对象的静态工厂方法-->
      <bean id="staticFactory" class="com.gpb.factory.UserStaticFactory" factory-method="userFactory"/>
    
    public class UserStaticFactory{
        public static User userFactory(){
            return new User();
        }
    }
    
  • 调用实例工厂方法创建Bean

    <!--通过实例化工厂创建,先创建实例化工厂对象-->
        <bean id="userFactory" class="com.gpb.factory.UserFactory"/>
    <!--   引入工厂对象的工厂方法创建User对象-->
        <bean id="user1" factory-bean="userFactory" factory-method="factory"/>
    
    public class UserFactory{
        public User factory(){
            return new User();
        }
    }
    
Bean的作用域

bean 是由 Spring IoC 容器实例化、组装和管理的对象。

  • 单例:singleton 默认的

  • 原型:prototype 每次都会获得不同的对象

  • request session global-session 等 :和 web 工厂的相同

    <bean id="user2" class="com.kuang.pojo.User" value="zz" scope="prototype "/>

  • 配置文件和注解实现

Spring对Bean的初始化和销毁

对于普通的 Java 对象,当 new 的时候创建对象,然后该对象就能够使用了。一旦该对象不再被使用,则由 Java 自动进行垃圾回收。

而 Spring 中的对象是 bean,bean 和普通的 Java 对象没啥大的区别,只不过 Spring 不再自己去 new 对象了,而是由 IoC 容器去帮助我们实例化对象并且管理它,我们需要哪个对象,去问 IoC 容器要即可。IoC 其实就是解决对象之间的耦合问题,Spring Bean 的生命周期完全由容器控制。

​ Spring Bean 的生命周期主要指的是 singleton bean,对于 prototype 的 bean ,Spring 在创建好交给使用者之 后则不会再管理后续的生命周期。

Spring Bean 的生命周期来说:

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

实例化 -> 属性赋值 -> 初始化 -> 销毁

  • 配置文件
  • 注解
依赖注入分类
  • 接口注入

    • 不常用,具有入侵性
  • setter方法注入

    • 这个属性必须具有 set 方法

    • <bean id="userDao" class="com.gpb.dao.UserDao"/>    
      <bean id="userService" class="com.gpb.service.UserService">    
       <!-- 这两个bean 都由ioc自动创建对象  UserService 这个类的属性是UserDao类型的 通过这种方式将userDao注入到UserService的属性userDao-->
              <property name="userDao" ref="userDao"></property>    
      </bean>    
      
  • 构造方法注入

    • 得有构造,默认无参,有参的话用 index 去给参数注入

    • <bean id="userDao" class="com.gpb.dao.UserDao"/>    
      <bean id="userService" class="com.gpb.service.UserService">    
       <!-- 这两个bean 都由ioc自动创建对象  UserService 这个类的属性是UserDao类型的 通过这种方式将userDao注入到UserService的属性userDao-->
              <constructor-arg index="0" ref="userDao"></property>    
      </bean>   
      
  • 注解方式注入

    采用注解的话需要引入 spring-aop.jar 包,并在配置文件中加上:<context:annotation-config></context:annotation-config>

    • @Autowired 按类型自动装配,可标注在成员变量(官方不推荐)、构造方法、setter方法上。
      • 标注在构造方法上时,可缺省@Autowired,因为使用包扫描时,如果未显式配置依赖注入,默认使用构造方法的自动装配(按参数类型)。
    • @Qualifier 按名称自动装配,需要和@Autowired搭配使用,只能标注在成员变量(官方不推荐)、setter方法上。
      • 需要在@Autowired("name")写上所依赖bean的name
    • @Resource 按名称或类型自动装配,需要第三方包 javax.annotation.jar 的支持,只能标注在成员变量、setter方法上。
      • 在后面括号设置:name的值是String形式,type的值是class形式。@Resource(name = "b"、type=N.class)
      • 默认先按名称装配,找不到满足的再按类型去装配
    • @Value 注入基本数据类型 只能标注在成员变量、setter上,不能标注在构造上

    稍微复杂一些的还是需要手动编写配置文件去注入

  • Bean的自动装配:Spring会在上下文中自动寻找 并自动给 bean 装配属性

    <bean id="people" class="com.gpb.pojo.People" autowrite="byName\byType">
         <property name="name" value="小白莲"/>
    </bean>
    

    ByName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致

    ByType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致

  • 拓展:我们可以使用p命名空间和c命名空间进行注入

    需要先加入 xml 约束:

           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
    
    <!--p命名空间注入,可以直接注入属性的值:property-->
    <bean id="user" class="com.gpb.pojo.User" p:name="黑心白莲" p:age="20"/>
    <!--c命名空间注入,通过构造器注入:constructor-args-->
    <bean id="user2" class="com.gpb.pojo.User" c:name="zz" c:age="22"/>
    
注解开发

注意配置文件的

采用注解的话需要引入 spring-aop.jar 包,并在配置文件中加上:<context:annotation-config></context:annotation-config>

需要配置包扫描,这个已经包括了启用注解注入的功能,所以不用单独去加入启动注解的配置了

<context:component-scan base-package="com.gpb" />

  • @Controller 控制层

  • @Service 业务层

  • @Repository 持久层

  • @Component 之前没有分区别的同一写法

    注意:交给Spring注册的类的对象名字默认是首字母小写的 也可以自己给名字 后面直接写

    其实他们都是一个意思 名字不同只是为了便于区分

  • @Scope("singleton") 作用域的注解

  • @Autowired 按类型注入

  • @Resource 按名称或者类型注入【web服务器提供的】

  • @Aspect 容器是切面的

  • @Nullable 字段标记了了这个注解,说明这个字段可以为null;

xml与注解:

  • xml更加万能,适用于任何场合!维护简单方便
  • 注解不是自己类使用不了,维护相队复杂!

xml与注解最佳实践:

  • xml用来管理bean;
  • 注解只负责完成属性的注入;
  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
使用Java 的方式去配置Spring

完全使用Java来做,不去配置文件配置!在Spring-Boot中很常见。

  • 实体类
//这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }

    @Value("黑心白莲") //属性注入值
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 配置文件

    // 这个也会Spring容器托管,注册到容器中,因为它本来就是一个@Component
    // @Configuration代表这是一个配置类,就和我们之前看的beans.xml
    // 扫包的注解
    // 反射的方式导入自己
    @Configuration
    @ComponentScan("com.gpb.pojo")
    @Import(UserConfig.class)
    public class UserConfig {
    
        // 注册一个bean,就相当于我们之前写的一个bean标签
        // 这个方法的名字,就相当于bean标签中id属性
        // 这个方法的返回值,就相当于bean标签中的class属性
        @Bean
        public User user(){
            return new User(); // 就是返回要注入到bean的对象!
        }
    }
    
  • 测试类

    public class MyTest {
        public static void main(String[] args) {
            //如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!
            ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
            User user = context.getBean("user", User.class);
            System.out.println(user.getName());
        }
    }
    

代理模式

分类
  • 静态代理
  • 动态代理
静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共角色就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低~
动态代理
  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:
    • 基于接口的动态代理,
      • JDK动态代理【我们在这里使用】
      • DK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
    • 基于类的动态代理
      • cglib
        • CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
        • CGLib原理是动态生成被代理类的子类。
        • cglib-nodep-2.2.jar:使用nodep包不需要关联asm的jar包,jar包内部包含asm的类.
        • cglib-2.2.jar:使用此jar包需要关联asm的jar包,否则运行时报错.
        • CGLib不能对声明为final的方法进行代理
  • java字节码实现:javassist
  • 需要了解两个类:Proxy:代理;InvocationHandler:调用处理程序。

动态代理的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共角色就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!

AOP编程

AOP(Aspect Oriented Programming)即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

image-20211125200641539

AOP核心概念

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

对连接点进行拦截的定义

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

6、目标对象

代理的目标对象

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

AOP在 Spring 中的使用

作用:提供声明式事务,允许用户自定义切面。

Spring通过 ADVICE 通知来定义横切逻辑,即使AOP在不改变原有代码的情况下去增加新的功能。

img image-20211125201105200

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

  • 默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

  • 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

  • 定义普通业务组件

  • 定义切入点,一个切入点可能横切多个业务组件

  • 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

Spring实现AOP

实现方式一:

  • 第一步:AOP 织入需要导入依赖包:*https://mvnrepository.com/artifact/org.aspectj/aspectjweaver*

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
    
  • 第二步:使用 Spring 的 API 接口实现:

    • 在service包下定义业务接口和业务实现类

    • package com.gpb.service;
      
      public interface UserService {
          public void add();
          public void delete();
          public void update();
          public void select();
      }
      
      package com.gpb.service;
      
      public class UserServiceImpl implements UserService{
          @Override
          public void add() {
              System.out.println("增加了一个用户!");
          }
      
          @Override
          public void delete() {
              System.out.println("删除了一个用户!");
          }
      
          @Override
          public void update() {
              System.out.println("修改了一个用户!");
          }
      
          @Override
          public void select() {
              System.out.println("查询一个用户!");
          }
      }
      
    • 在log包下,定义增强类,一个Log前置增强和一个AfterLog后置增强类

    • package com.gpb.log;
      
      import org.springframework.aop.AfterReturningAdvice;
      
      import java.lang.reflect.Method;
      
      public class AfterLog implements AfterReturningAdvice {
      
          @Override
          public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
              System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
          }
      }
      
      package com.gpb.log;
      
      import org.springframework.aop.MethodBeforeAdvice;
      
      import java.lang.reflect.Method;
      
      public class Log implements MethodBeforeAdvice {
          /**
           * @param method  要执行的目标对象的方法
           * @param objects 参数
           * @param o 目标对象
           * @throws Throwable
           */
          @Override
          public void before(Method method, Object[] objects, Object o) throws Throwable {
              System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
          }
      }
      
      
  • 第三步:spring 的配置文件中注册 实现 AOP 的切入 注意导入约束

    <!--注册bean-->
        <bean id="userService" class="com.kuang.service.UserServiceImpl"/>
        <bean id="log" class="com.kuang.log.Log"/>
        <bean id="afterLog" class="com.kuang.log.AfterLog"/>
    
        <!--方式一:使用原生Spring API接口-->
        <!--配置aop:需要导入aop的约束-->
        <aop:config>
            <!--切入点:expression:表达式,execution(要执行的位置!* * * * *)-->
            <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
    
            <!--执行环绕增加!-->
            <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
            <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
        </aop:config>
    
  • 第四步:测试

    package com.gpb.test;
    
    import com.gpb.service.UserService;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AopTest {
        ApplicationContext context = null;
    
        @Before
        public void setUp(){
            context = new ClassPathXmlApplicationContext("applicationContext.xml");
        }
    
        @Test
        public void add(){
            // 动态代理的是代理接口
            UserService userService = (UserService) context.getBean("userService");
            userService.add();
        }
    
        @After
        public void tearDown(){
    
        }
    }
    

第二种实现方式:自定义类来实现AOP【定义】

  • 其它配置与上面相似

  • 自定义切点类

    package com.gpb.pointcut;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyCut {
        public void before(){
            System.out.println("======方法执行前======");
        }
    
        public void after(){
            System.out.println("======方法执行后======");
        }
    }
    
  • 配置

        <!-- 自定义切点类  @Component-->
        <aop:config>
            <!--自定义切面,ref 要引用的类-->
            <aop:aspect ref="myCut">
                <!--切入点-->
                <aop:pointcut id="point" expression="execution(* com.gpb.service.UserServiceImpl.*(..))"/>
                <!--通知-->
                <aop:before method="before" pointcut-ref="point"/>
                <aop:after method="after" pointcut-ref="point"/>
            </aop:aspect>
        </aop:config>
    
  • 测试

第三种实现方式注解:

  • 定义切面类 【@Aspect

    //声明式事务!
    @Aspect //标注这个类是一个切面
    public class AnnotationPointCut {
    
        @Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
        public void before(){
            System.out.println("====方法执行前====");
        }
    
        @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
        public void after(){
            System.out.println("====方法执行后====");
        }
    
        //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点;
        @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
        public void around(ProceedingJoinPoint jp) throws Throwable{
            System.out.println("环绕前");
    
            Signature signature = jp.getSignature();// 获得签名
            System.out.println("signature:"+signature);
    
            Object proceed = jp.proceed(); //执行方法
    
            System.out.println("环绕后");
    
            System.out.println(proceed);
        }
    }
    
  • 配置:

     <!--方式三:使用注解-->
        <bean id="annotationPointCut" class="com.kuang.diy.AnnotationPointCut"/>
        <!--开启注解支持! JDK(默认是 proxy-target-class="false")  cglib(proxy-target-class="true")-->
        <aop:aspectj-autoproxy/>
    
  • 测试

注解的使用:

image-20211125211237228
  • @EnableAspectJAutoProxy :开启自动代理 这样 切面就能够被织入到Spring管理的类中了

  • @Aspect :标注该类是个切面

    • aspect里面有一个order属性,order属性的数字就是横切关注点的顺序
  • @Before/After("execution(* com.kuang.service.UserServiceImpl.*(..))"):去表示在什么地方之前之后去做

  • @Pointcut("execution(* com.kuang.service.UserServiceImpl.*(..))") :能够在切面内定义可重用的切点

  • @Around :创建环绕通知

Spring的事务管理

事务复习
  • 理解

    • 把一组业务当成一个业务来做;要么都成功,要么都失败!

    • 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!

    • 确保完整性和一致性。

  • 事务ACID原则:

    • 原子性(atomicity)

      • 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用。
    • 一致性(consistency)

      • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中。
    • 隔离性(isolation)

      • 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
    • 持久性(durability)

      • 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中。
Spring的事务

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

  • 编程式事务管理

    • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚

    • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

  • 声明式事务管理

    • 一般情况下比编程式事务好用。

    • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

    • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务
  • 头文件的约束导入 : tx**

    xmlns:tx="http://www.springframework.org/schema/tx"
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd"
    
  • 事务配置

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
    </bean>
    
  • 通知配置

    <!--配置事务通知-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!--给那些方法配置事务-->
            <!--配置事务的传播特性: new -->
            <tx:attributes>
                <tx:method name="add" propagation="REQUIRED"/>
                <tx:method name="delete" propagation="REQUIRED"/>
                <tx:method name="update" propagation="REQUIRED"/>
                <tx:method name="query" read-only="true"/>
                <tx:method name="*" propagation="REQUIRED"/>
            </tx:attributes>
        </tx:advice>
    
  • 配置事务的AOP 横向切入

    <!--配置事务切入-->
        <aop:config>
            <aop:pointcut id="txPointCut" expression="execution(* mapper.*.*(..))"/>
            <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
        </aop:config>
    

注解:

IOC容器创建Bean
  • @Service:业务层的Bean组件
  • @Controller:控制层Bean组件
  • @Repository:持久层的Bean组件
  • @Component:其它Bean组件

​ 默认bean的名字是 类首字母小写的

DI依赖注入
  • @Autowired:依据类型自动注入
  • @Qualifier:辅助上面的 后面要指明名称 用类型+名称来注入bean
  • @Resource:先依据名字去注入 找不到的话再依据类型去注入
配置类
  • @Configuration:配置类
  • @ImportResource:引入配置文件
  • @Bean:表明Bean组件
  • @Scope:Bean作用域配置【singleton,prototype,session,request...
AOP
  • @Aspect:表明是一个切面类
  • @Pointcut:表明切入点【execution(* ...方法完全限定名)
  • @Transform:添加事务
  • 11
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值