65-Spring进阶2-基于注解的Spring应用 + Spring的AOP开发

81 阅读5分钟

Spring进阶2-基于注解的Spring应用 + Spring的AOP开发

笔记内容来源于黑马程序员教学视频

一、基于注解的Spring应用

①:Bean基本注解开发

1. @Component的使用

Spring除了xml配置文件进行配置之外,还可以使用注解方式进行配置,注解方式慢慢成为xml配置的替代方案。我们有了xml开发的经验,学习注解开发就方便了许多,注解开发更加快捷方便。

Spring提供的注解有三个版本:

  • 2.0时代,Spring开始出现注解
  • 2.5时代,Spring的Bean配置可以使用注解完成
  • 3.0时代,Spring其他配置也可以使用注解完成,我们进入全注解时代

基本Bean注解,主要是使用注解的方式替代原有xml的 <bean> 标签及其标签属性的配置

<bean id="" name="" class="" scope="" lazy-init="" init-method="" 
      destroy-method="" abstract="" autowire="" factory-bean="" 
      factory-method="">
</bean>

使用@Component 注解替代<bean>标签

image.png

可以通过@Component注解的value属性指定当前Bean实例的beanName,也可以省略不写,不写的情况下为当前类名首字母小写

  • UserDaoImpl
@Component
public class UserDaoImpl implements UserDao {
}
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:haohao="http://www.itCoke.com/haohao"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.itCoke.com/haohao
       http://www.itCoke.com/haohao/haohao-annotation.xsd">

<!-- 告知Spring框架去it包及其子包下去扫描使用了注解的类 -->
 <context:component-scan base-package="com.it"/>
 
</beans>
  • 测试
@Test
public void test1(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDaoImpl userDaoImpl = (UserDaoImpl) context.getBean("userDaoImpl");
    System.out.println("userDaoImpl = " + userDaoImpl);
}

image.png

2. 作用范围等注解的使用

@Component就单纯一个value属性,那么xml配置 <bean> 时那些属性怎么进行配置呢?Spring 是通过注解方式去配置的之前 <bean> 标签中的那些属性,例如:@Scope

<bean id="" name="" class="" scope="" lazy-init="" init-method="" 
      destroy-method="" abstract="" autowire="" factory-bean="" 
      factory-method="">
</bean>

使用@Component 注解替代<bean>标签

image.png

使用上述注解完成UserDaoImpl的基本配置

@Component
@Scope("singleton")
@Lazy(false)
public class UserDaoImpl implements UserDao {

    public UserDaoImpl() {
        System.out.println("UserDaoImpl执行了无参构造~~~");
    }

    @PostConstruct
    public void init(){
        System.out.println("UserDaoImpl执行了初始化方法~~~");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("UserDaoImpl执行了销毁方法~~~");
    }
}
  • 测试
@Test
public void test1(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDaoImpl userDaoImpl = (UserDaoImpl) context.getBean("userDaoImpl");
    System.out.println("userDaoImpl = " + userDaoImpl);

    context.close();
}

image.png

3. @Component 三个衍生注解

由于JavaEE开发是分层的,为了每层Bean标识的注解语义化更加明确,@Component又衍生出如下三个注解:

@Component衍生注解描述
@Repository在Dao层类上使用
@Service在Service层类上使用
@Controller在Web层类上使用
@Repository("userDao")
public class UserDaoImpl implements UserDao{}

@Service("userService")
public class UserServiceImpl implements UserService{}

@Controller("userService")
public class UserController {}

②:Bean依赖注入注解开发

Bean依赖注入的注解,主要是使用注解的方式替代xml的<property>标签完成属性的注入操作

<bean id="" class="">
    <property name="" value=""/>
    <property name="" ref=""/>
</bean>

Spring主要提供如下注解,用于在Bean内部进行属性注入的:

属性注入注解描述
@Value使用在字段或方法上,用于注入普通数据
@Autowired使用在字段或方法上,用于根据类型(byType)注入引用数据
@Qualifier使用在字段或方法上,结合@Autowired,根据名称注入
@Resource使用在字段或方法上,根据类型或名称进行注入

1.通过@Value直接注入普通属性\color{#00FF00}{1. 通过@Value 直接注入普通属性}

@Value("小🐋🐋")
private String userName;

2.通过@Value注入properties文件中的属性\color{#00FF00}{2. 通过@Value 注入properties文件中的属性}

  • jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
jdbc.password=root
jdbc.username=root
  • applicationContext.xml(加载properties文件)
<context:property-placeholder location="classpath:jdbc.properties"/>
  • 通过@Value 注入properties文件中的属性
@Value("${jdbc.driver}")
private String driver;

@Value("${jdbc.username}")
private String username;

3.@Autowired注解,用于根据类型进行注入\color{#00FF00}{3. @Autowired注解,用于根据类型进行注入}

//使用在属性上直接注入
@Autowired
private UserServiceImpl userServiceImpl;

当容器中同一类型的Bean实例有多个时,会尝试自动根据名字进行匹配:

//使用在属性上直接注入
@Autowired
private UserServiceImpl userServiceImpl;

@Autowired
private UserServiceImpl2 userServiceImpl2;

当容器中同一类型的Bean实例有多个时,且名字与被注入Bean名称不匹配时会报错 4.@Qualifier配合@Autowired可以完成根据名称注入Bean实例,使\color{#00FF00}{4. @Qualifier配合@Autowired可以完成根据名称注入Bean实例,使} @Qualifier指定名称\color{#00FF00}{用@Qualifier指定名称}

//使用在属性上直接注入
@Autowired()
@Qualifier("userServiceImpl")
private UserService userServiceImpl;

@Autowired
@Qualifier("userServiceImpl2")
private UserService userServiceImpl2;

5.@Resource注解既可以根据类型注入,也可以根据名称注入,无参就是根据\color{#00FF00}{5. @Resource注解既可以根据类型注入,也可以根据名称注入,无参就是根据} 类型注入,有参数就是根据名称注入\color{#00FF00}{类型注入,有参数就是根据名称注入}

//使用在属性上直接注入
@Resource(name = "userServiceImpl")
private UserServiceImpl userServiceImpl;

@Resource(name = "userServiceImpl2")
private UserService userServiceImpl2;

PS:@Resource注解存在与 javax.annotation 包中,Spring对其进行了解析

6.@Autowired用在方法上\color{#00FF00}{6. @Autowired用在方法上}

@Autowired
public void xxx(List<UserService> userServiceList){
    System.out.println("xxx = " + userServiceList);
}

image.png

③:非自定义Bean注解开发

非自定义Bean不能像自定义Bean一样使用@Component进行管理,非自定义Bean要通过工厂的方式进行实例化,使用@Bean标注方法即可,@Bean的属性为beanName,如不指定为当前工厂方法名称

如果@Bean工厂方法需要参数的话,则有如下几种注入方式:

  • 使用@Autowired 根据类型自动进行Bean的匹配,@Autowired可以省略(仅限于非自定义Bean,自定义Bean不能省略) ;
  • 使用@Qualifier 根据名称进行Bean的匹配;
  • 使用@Value 根据名称进行普通数据类型匹配。
@Component
public class OtherBean {
    // 将方法返回值Bean实例以@Bean注解指定的名称存储到Spring容器中
    @Bean
    public DataSource dataSource(
           @Value("${jdbc.driver}") String driverClassName,
           @Qualifier("userServiceImpl") UserService userServiceImpl,
            UserServiceImpl2 userServiceImpl2
    ){
        DruidDataSource dataSource = new DruidDataSource();
        System.out.println("driverClassName = " + driverClassName);
        System.out.println("userServiceImpl = " + userServiceImpl);
        System.out.println("userServiceImpl2 = " + userServiceImpl2);
        return dataSource;
    }
}

PS:工厂方法所在类必须要被Spring管理

④:Bean配置类的注解开发

@Component等注解替代了<bean>标签,但是像<import>、<context:componentScan> 等非<bean> 标签怎样去使用注解替代呢?

<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 组件扫描 -->
<context:component-scan base-package="com.itheima"/>
<!-- 引入其他xml文件 -->
<import resource="classpath:beans.xml"/>

1.定义一个配置类替代原有的xml配置文件,<bean>标签以外的标签,一般都\color{#00FF00}{1. 定义一个配置类替代原有的xml配置文件,<bean>标签以外的标签,一般都} 是在配置类上使用注解完成的\color{#00FF00}{是在配置类上使用注解完成的}

@Configuration
public class SpringConfig {
}

2.@ComponentScan组件扫描配置,替代原有xml文件中的\color{#00FF00}{2. @ComponentScan 组件扫描配置,替代原有xml文件中的} <context:componentscanbasepackage=""/>\color{#00FF00}{<context:component-scan base-package=""/>}

@Configuration
//@ComponentScan(basePackages = {"com.it"}) // 可以配置多个
@ComponentScan("com.it") // 简写
public class SpringConfig {
}

base-package的配置方式:

  • 指定一个或多个包名:扫描指定包及其子包下使用注解的类

  • 不配置包名:扫描当前@componentScan注解配置类所在包及其子包下的类

3.@PropertySource注解用于加载外部properties资源配置,替代原有\color{#00FF00}{3. @PropertySource 注解用于加载外部properties资源配置,替代原有} xml中的<context:propertyplaceholderlocation=“”/>配置\color{#00FF00}{xml中的 <context:property-placeholder location=“”/> 配置}

@Configuration
//@ComponentScan(basePackages = {"com.it"}) // 可以配置多个
@ComponentScan("com.it") // 简写
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}

4.@Import用于加载其他配置类,替代原有xml中的<import\color{#00FF00}{4. @Import 用于加载其他配置类,替代原有xml中的<import } resource=classpath:beans.xml/>配置\color{#00FF00}{ resource=“classpath:beans.xml”/>配置}

@Configuration
//@ComponentScan(basePackages = {"com.it"}) // 可以配置多个
@ComponentScan("com.it") // 简写
@PropertySource("classpath:jdbc.properties")
@Import(OtherBean.class)
public class SpringConfig {
}
  • 测试
    @Test
    public void test2(){
        // xml 方式的Spring容器
        // ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 注解方式区加载Spring的核心配置类
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        Object dataSource = context.getBean("dataSource");
        System.out.println("dataSource = " + dataSource);
    }

⑤:Spring 配置其他注解

1.@Primary注解\color{#00FF00}{1. @Primary注解}

@Primary注解用于标注相同类型的Bean优先被使用权,@Primary 是Spring3.0引入的,与@Component和@Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过@Autowired根据类型进行注入时,会选用优先级更高的

@Repository("userDao")
public class UserDaoImpl implements UserDao{}
@Repository("userDao2")
@Primary
public class UserDaoImpl2 implements UserDao{}
@Bean
public UserDao userDao01(){return new UserDaoImpl();}
@Bean
@Primary
public UserDao userDao02(){return new UserDaoImpl2();}    

2.@Profile注解\color{#00FF00}{2. @Profile 注解}

@Profile 注解的作用同于xml配置时学习profile属性,是进行环境切换使用的

<beans profile="test">    

注解 @Profile 标注在类或方法上,标注当前产生的Bean从属于哪个环境,只有激活了当前环境,被标注的Bean才能被注册到Spring容器里,不指定环境的Bean,任何环境下都能注册到Spring容器里

@Repository("userDao")
@Profile("test")
public class UserDaoImpl implements UserDao{}
    
@Repository("userDao2")
public class UserDaoImpl2 implements UserDao{}    

可以使用以下两种方式指定被激活的环境:

  • 使用命令行动态参数,虚拟机参数加载-Dspring.profiles.active=test

  • 使用代码的方式设置环境变量 System.setProperty("spring.profiles.active","test");

⑥:Spring注解的解析原理

image.png

结论:只要将Bean对应的BeanDefinition注册到beanDefinitionMap中,就可以经历整个 SpringBean的生命周期,最终实例化进入单例池中

使用@Component等注解配置完毕后,要配置组件扫描才能使注解生效

  • xml配置组件扫描:
<context:component-scan base-package="com.itheima"/>
  • 配置类配置组件扫描:
@Configuration
@ComponentScan("com.itheima")
public class AppConfig {
}    

使用xml方式配置组件扫描,而component-scan是一个context命名空间下的自定义标签,所以要找到对应的命名空间处理器NamespaceHandler 和 解析器,查看spring-context包下的spring.handlers文件

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler    

查看 ContextNamespaceHandler 类

public void init() {
    this.registerBeanDefinitionParser("component-scan", new
    ComponentScanBeanDefinitionParser());
}    

将ComponentScanBeanDefinitionParser进行了注册,对其源码进行跟踪,最终将标注的@Component的类,生成对应的BeanDefiition进行了注册

使用配置类配置组件扫描,使用AnnotationConfigApplicationContext容器在进行创建时,内部调用了如下代码,该工具注册了几个Bean后处理器:

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);    

image.png

其中,ConfigurationClassPostProcessor 是一个 BeanDefinitionRegistryPostProcessor ,经过一系列源码调用,最终也别指定到了ClassPathBeanDefinitionScanner 的doScan 方法(与xml方式最终终点一致)

image.png

⑦:Spring注解方式整合第三方框架

1. 代码实现

  • 导入坐标
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.7</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.23</version>
</dependency>
  • 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    private Long id;
    private String name;
    private String email;
    private int deleted;
}
  • UserMapper
public interface UserMapper {
    List<User> findAll();
}
  • UserService 和 ServiceImpl
public interface UserService {

    List<User> findAll();
}
@Service
public class UserServiceImpl implements UserService {

    @Autowired(required = false)
    private UserMapper userMapper;

    @Override
    public List<User> findAll() {
        return userMapper.findAll();
    }
}
  • UserMapper.xmlresources目录下创建路径于UserMapper类路径保持一致
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.UserMapper">
<select id="findAll" resultType="com.it.pojo.User">
        select * from user
</select>
</mapper>
  • SpringConfig
@Configuration
@ComponentScan("com.it")
@PropertySource("classpath:jdbc.properties")
@MapperScan("com.it.mapper")
public class SpringConfig {
    @Bean
    public DataSource dataSource(
            @Value("${jdbc.driver}") String driverClassName,
            @Value("${jdbc.url}") String url,
            @Value("${jdbc.username}") String username,
            @Value("${jdbc.password}") String password
    ){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
}
  • 测试
public static void main(String[] args) {
    // 注解方式区加载Spring的核心配置类
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
    List<User> userList = userServiceImpl.findAll();
    userList.forEach(System.out::println);
}

2. 注解方式整合MyBatis的原理

注解方式,Spring整合MyBatis的原理,关键在于@MapperScan,@MapperScan不是Spring提供的注解,是MyBatis为了整合Spring,在整合包org.mybatis.spring.annotation中提供的注解,源码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
    String[] value() default {};
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<? extends Annotation> annotationClass() default Annotation.class;
    // ... ...
}    

重点关注一下@Import({MapperScannerRegistrar.class}),当@MapperScan被扫描加载时,会解析@Import注解,从而加载指定的类,此处就是加载了MapperScannerRegistrar


MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,Spring会自动调用 registerBeanDefinitions方法,该方法中又注册MapperScannerConfigurer类,而MapperScannerConfigurer类作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,前面讲过

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware {
    //默认执行registerBeanDefinitions方法
    void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
    BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        //... 省略其他代码 ...
        //注册BeanDefinition
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }
}

3. @Import整合三方框架原理

Spring与MyBatis注解方式整合有个重要的技术点就是@Import,第三方框架与Spring整合xml方式很多是凭借自定义标签完成的,而第三方框架与Spring整合注解方式很多是靠@Import注解完成的

@Import可以导入如下三种类:

  • 普通的配置类
  • 实现ImportSelector接口的类
  • 实现ImportBeanDefinitionRegistrar接口的类

@Import导入实现了ImportSelector接口的类

@Configuration
@ComponentScan("com.itheima")
@Import({MyImportSelector.class})
public class ApplicationContextConfig {
}
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //返回要进行注册的Bean的全限定名数组
        return new String[]{User2.class.getName()};
    }
}

ImportSelector接口selectImports方法的参数AnnotationMetadata代表注解的媒体数据,可以获得当前注解修饰的类的元信息,例如:获得组件扫描的包名

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //获得指定类型注解的全部信息
        Map<String, Object> annotationAttributes =
        annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName());
        //获得全部信息中basePackages信息
        String[] basePackages = (String[]) annotationAttributes.get("basePackages");
        //打印结果是com.itheima
        System.out.println(basePackages[0]);
        return new String[]{User2.class.getName()};
    }
}

@Import导入实现ImportBeanDefinitionRegistrar接口的类,实现了该接口的类的registerBeanDefinitions方法会被自动调用,在该方法内可以注册BeanDefinition

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
    BeanDefinitionRegistry registry) {
        //使用给定的BeanDefinitionRegistry参数,手动注册BeanDefinition
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.itheima.pojo.User2");
        registry.registerBeanDefinition("user2",beanDefinition);
    }
}

二、Spring的AOP开发

①:AOP简介

1. 概念

AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程

image.png

2. AOP思想实现方案

动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入执行其他增强逻辑或其他对象的方法

image.png

3. 模拟AOP的基础代码

image.png

  • 导入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.7</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.23</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>
  • UserService 和 UserServiceImpl
public interface UserService {

    void show1();

    void show2();
}
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1================>");

    }

    @Override
    public void show2() {
        System.out.println("show2================>");
    }
}
  • MyAdvice
// 增强类,内部提供增强方法
public class MyAdvice {

    public void beforeAdvice(){
        System.out.println("前置的增强..........");
    }

    public void afterAdvice() {
        System.out.println("后置的增强..........");
    }
}
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="myAdvice" class="com.it.advice.MyAdvice"/>
    <bean id="userService" class="com.it.service.impl.UserServiceImpl"/>

    <bean id="postProcessor" class="com.it.processor.MockAopBeanPostProcessor"/>
</beans>
  • MockAopBeanPostProcessor
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 目的:UserServiceImpl中的show1和show2方法进行增强,增强方法存在MyAdvice中
        // 问题1:筛选service.impl包下的所有的类的所有方法都可以进行增强,解决方案if-else
        // 问题2:MyAdvice.怎么获取到?解决方案:从Spring容器中获得yAdvice
        if (bean.getClass().getPackage().getName().equals("com.it.service.impl")){
            // 生成当前Bean的Proxy对象
            Object proxyInstance = Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (o, method, objects) -> {
                        MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);
                        // 执行增强对象的before方法
                        myAdvice.beforeAdvice();
                        // 执行目标对象的目标方法
                        Object result = method.invoke(bean, objects);
                        // 执行增强对象的after方法
                        myAdvice.afterAdvice();
                        return result;
                    }
            );
            return proxyInstance;
        }
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
  • 测试
public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = context.getBean(UserService.class);
    userService.show1();
}

image.png

4. AOP相关概念

image.png

image.png

②:基于xml配置的AOP

1. xml方式AOP快速入门

  • 导入依赖
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
  • MyAdvice类
// 增强类,内部提供增强方法
public class MyAdvice {

    public void beforeAdvice(){
        System.out.println("前置的增强..........");
    }

    public void afterAdvice() {
        System.out.println("后置的增强..........");
    }
}
  • UserServiceImpl
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1================>");

    }

    @Override
    public void show2() {
        System.out.println("show2================>");
    }
}
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="myAdvice" class="com.it.advice.MyAdvice"/>
    <bean id="userService" class="com.it.service.impl.UserServiceImpl"/>

<!--    <bean id="postProcessor" class="com.it.processor.MockAopBeanPostProcessor"/>-->

<!--    aop配置-->
    <aop:config>
<!--        配置切点表达式,目的式要指定哪些方法被增强-->
        <aop:pointcut id="myPointcut" expression="execution(void com.it.service.impl.UserServiceImpl.show1())"/>
<!--        配置织入,目的是要执行哪些切点于哪些通知进行结合-->
        <aop:aspect ref="myAdvice">
            <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  • 测试
@Test
public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = context.getBean(UserService.class);
    userService.show1();
}

image.png

2. xml方式AOP配置详解

xml配置AOP的方式还是比较简单的,下面看一下AOP详细配置的细节:

  • 切点表达式的配置方式

  • 切点表达式的配置语法

  • 通知的类型

  • AOP的配置的两种方式

1.切点表达式的配置方式\color{#00FF00}{1. 切点表达式的配置方式}

  • 方式一:
<!--    aop配置-->
   <aop:config>
<!--        配置切点表达式,目的式要指定哪些方法被增强-->
       <aop:pointcut id="myPointcut" expression="execution(void com.it.service.impl.UserServiceImpl.show1())"/>
<!--        配置织入,目的是要执行哪些切点于哪些通知进行结合-->
       <aop:aspect ref="myAdvice">
           <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
       </aop:aspect>
   </aop:config>
  • 方式二:
<!--    aop配置-->
<aop:config>
    <!--        配置切点表达式,目的式要指定哪些方法被增强-->
    <!--        配置织入,目的是要执行哪些切点于哪些通知进行结合-->
    <aop:aspect ref="myAdvice">
        <aop:before method="beforeAdvice" pointcut="execution(void com.it.service.impl.UserServiceImpl.show1())"/>
    </aop:aspect>
</aop:config>

2.切点表达式的配置语法\color{#00FF00}{2. 切点表达式的配置语法}

切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:

execution'([访问修饰符]返回值类型包名.类名.方法名(参数))       

其中,

  • 访问修饰符可以省略不写;

  • 返回值类型、某一级包名、类名、方法名可以使用*表示任意:

  • 包名与类名之间使用单点.表示该包下的类,使用双点.表示该包及其子包下的类:

  • 参数列表可以使用两个点.表示任意参数。

切点表达式举几个例子方便理解

//表示访问修饰符为oublic、无返回值、在com.itheima.aop包下的TargetImpl类的无参方法show
execution (public void com.itheima.aop.TargetImpl.show ()
    
//表述com.itheima.aop包下的TargetImpl类的任意方法
execution (com.itheima.aop.TargetImpl.*(..))
    
//表示com.itheima.aop包下的任意类的任意方法
execution (com.itheima.aop.*.*(..))
    
//表示com.itheima.aop包及其子包下的任意类的任意方法
execution (com.itheima.aop..*.*(..))
    
//表示任意包,中的任意类的任意方法
execution (*..*.*(..)    

3.通知的类型\color{#00FF00}{3. 通知的类型}

AspectJ的通知由以下五种类型

通 知 名 称配置方式执行时机
前置通知<aop:before>目标方法执行之前执行
后置通知<aop:after-returning >目标方法执行之后执行,目标方法异常时,不在执行
环绕通知< aop:around>目标方法执行前后执行,目标方法异常时,环绕后方法不在执行
异常通知<aop:after-throwing >目标方法抛出异常时执行
最终通知<aop:after>不管目标方法是否有异常,最终都会执行

1.前置通知 和 后置通知

  • MyAdvice
// 增强类,内部提供增强方法
public class MyAdvice {

    public void beforeAdvice(){
        System.out.println("前置的增强..........");
    }

    public void afterReturningAdvice() {
        System.out.println("后置的增强..........");
    }

    public void afterAdvice() {
        System.out.println("最终的增强..........");
    }
}
  • applicationContext.xml
<!--    aop配置-->
<aop:config>
    <!--        配置切点表达式,目的式要指定哪些方法被增强-->
    <aop:pointcut id="myPointcut" expression="execution(* com.it.service.impl.*.*(..))"/>
    
    <!--        配置织入,目的是要执行哪些切点于哪些通知进行结合-->
    <aop:aspect ref="myAdvice">
        <!-- 前置通知-->
        <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
        <!-- 后置通知-->
        <aop:after-returning method="afterReturningAdvice" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>
  • 测试

image.png

2.环绕通知

  • MyAdvice
public class MyAdvice {

    .....
    
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 环绕前的增强
        System.out.println("环绕前的增强~~~~~~~");
        // 执行目标方法
        Object proceed = proceedingJoinPoint.proceed();
        // 环绕后的增强
        System.out.println("环绕后的增强~~~~~~~");
        return proceed;
    }
}
  • applicationContext.xml
<!--    aop配置-->
<aop:config>
    <!--        配置切点表达式,目的式要指定哪些方法被增强-->
    <aop:pointcut id="myPointcut" expression="execution(* com.it.service.impl.*.*(..))"/>
    
    <aop:aspect ref="myAdvice">
        <!-- 环绕通知-->
        <aop:around method="around" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>

3.异常通知

  • MyAdvice
// 增强类,内部提供增强方法
public class MyAdvice {

    .....

    public void afterThrowingAdvice() {
        System.out.println("异常通知..........");
    }
}
  • applicationContext.xml
<!--    aop配置-->
<aop:config>
    <!--        配置切点表达式,目的式要指定哪些方法被增强-->
    <aop:pointcut id="myPointcut" expression="execution(* com.it.service.impl.*.*(..))"/>

    <aop:aspect ref="myAdvice">
        <!-- 异常通知-->
        <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>
  • UserServiceImpl
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1================>");
        int i = 1/0;
    }

    @Override
    public void show2() {
        System.out.println("show2================>");
    }
}

4.最终通知

  • MyAdvice
// 增强类,内部提供增强方法
public class MyAdvice {
    
    .... 
    
    public void afterAdvice() {
        System.out.println("最终的增强..........");
    }
}
  • applicationContext.xml
<!--    aop配置-->
<aop:config>
    <!--        配置切点表达式,目的式要指定哪些方法被增强-->
    <aop:pointcut id="myPointcut" expression="execution(* com.it.service.impl.*.*(..))"/>

    <aop:aspect ref="myAdvice">
        <!-- 最终通知-->
        <aop:after method="afterAdvice" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>

4.通知方法在被调用时,Spring可以为其传递一些必要的参数\color{#00FF00}{4. 通知方法在被调用时,Spring可以为其传递一些必要的参数}

image.png

1.JoinPoint

  • MyAdvice
// 增强类,内部提供增强方法
public class MyAdvice {

    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("当前目标对象是: " + joinPoint.getTarget());
        System.out.println("表达式: " + joinPoint.getStaticPart());
        System.out.println("前置的增强..........");
    }

    ......
}
  • applicationContext.xml
<!--    aop配置-->
    <aop:config>
<!--        配置切点表达式,目的式要指定哪些方法被增强-->
        <aop:pointcut id="myPointcut" expression="execution(void com.it.service.impl.UserServiceImpl.show1())"/>
<!--        配置织入,目的是要执行哪些切点于哪些通知进行结合-->
        <aop:aspect ref="myAdvice">
            <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
  • 测试

image.png

2.ProceedingJoinPoint(上面通知异常使用的就是)

3.Throwable

  • MyAdvice
// 增强类,内部提供增强方法
public class MyAdvice {

    ....
    
    public void afterThrowingAdvice(Throwable e) {
        System.out.println("异常对象是:" + e + "异常信息是:" + e.getMessage());
        System.out.println("异常通知..........");
    }
}
  • applicationContext.xml
<!--    aop配置-->
<aop:config>
    <!--        配置切点表达式,目的式要指定哪些方法被增强-->
    <aop:pointcut id="myPointcut" expression="execution(* com.it.service.impl.*.*(..))"/>

    <aop:aspect ref="myAdvice">
        <!-- 异常通知-->
        <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myPointcut" throwing="e"/>
    </aop:aspect>
</aop:config>
  • 测试

image.png

5.AOP的配置的两种方式\color{#00FF00}{5. AOP的配置的两种方式}

AOP的xml有两种配置方式,如下:

  • 使用<advisor>配置切面
  • 使用<aspect>配置切面

Spring定义了一个Advice接口,实现了该接口的类都可以作为通知类出现

public interface Advice {
」    

1.前置和后置通知

  • MyAdvice2
// 增强类,内部提供增强方法
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置通知~~~~~~~~~");
    }

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("后置通知~~~~~~~~~");
    }
}
  • applicationContext2.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="myAdvice2" class="com.it.advice.MyAdvice2"/>
    <bean id="userService" class="com.it.service.impl.UserServiceImpl"/>

    <!--    aop配置-->
    <aop:config>
        <!--        配置切点表达式,目的式要指定哪些方法被增强-->
        <aop:pointcut id="myPointcut2" expression="execution(* com.it.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="myAdvice2" pointcut-ref="myPointcut2"/>
    </aop:config>
</beans>
  • 测试

image.png

2.环绕通知

  • MyAdvice2
// 增强类,内部提供增强方法
public class MyAdvice2 implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        // 环绕前
        System.out.println("环绕前~~~~~");

        // 执行目标方法
        Object aThis = methodInvocation.getThis();
        Object[] arguments = methodInvocation.getArguments();
        Object result = methodInvocation.getMethod().invoke(aThis, arguments);

        // 环绕后
        System.out.println("环绕后~~~~~");
        return result;
    }
}

image.png

AOP配置的两种语法形式不同点

语法形式不同:

  • advisor是通过实现接口来确认通知的类型
  • aspect是通过配置确认通知的类型,更加灵活

可配置的切面数量不同:

  • 一个advisor只能配置一个固定通知和一个切点表达式
  • 一个aspect可以配置多个通知和多个切点表达式任意组合

使用场景不同:

  • 允许随意搭配情况下可以使用aspect进行配置

  • 如果通知类型单一、切面单一的情况下可以使用advisor进行配置

  • 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如后面要学习的 Spring事务控制的配置

3. xml方式AOP原理剖析

动态代理的实现的选择,在调用getProxy()方法时,我们可选用的 AopProxy接口有两个实现类,如上图,这两种都是动态生成代理对象的方式,一种就是基于JDK的,一种是基于Cglib的

image.png

image.png

image.png

③:基于注解配置的AOP

1. 注解方式AOP基本使用

Spring的AOP也提供了注解方式配置,使用相应的注解替代之前的xml配置,xml配置AOP时,我们主要配置了三部分:目标类被Spring容器管理、通知类被Spring管理、通知与切点的织入(切面),如下:

image.png

1. 注解开发AOP快速入门

  • MyAdvice
// 增强类,内部提供增强方法
@Component
@Aspect
public class MyAdvice {

    // <aop:pointcut id="myPointcut2" expression="execution(* com.it.service.impl.*.*(..))"/>
    @Before("execution(* com.it.service.impl.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("当前目标对象是: " + joinPoint.getTarget());
        System.out.println("表达式: " + joinPoint.getStaticPart());
        System.out.println("前置的增强..........");
    }
    .....
}
  • UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1================>");
    }

    @Override
    public void show2() {
        System.out.println("show2================>");
    }
}
  • applicationContext3.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--    使用注解配置AOP,需要开启AOP自动代理-->
    <aop:aspectj-autoproxy/>

<!--    组件扫描-->
    <context:component-scan base-package="com.it"/>
</beans>
  • 测试
public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
    UserService userService = context.getBean(UserService.class);
    userService.show1();
}

image.png

2. 注解方式AOP配置详解

image.png

image.png

1.切面表达式抽取

// 增强类,内部提供增强方法
@Component
@Aspect
public class MyAdvice {

    // 切面表达式抽取
    @Pointcut("execution(* com.it.service.impl.*.*(..))")
    public void MyPointcut(){ }

    // <aop:pointcut id="myPointcut2" expression="execution(* com.it.service.impl.*.*(..))"/>
    //  @Before("execution(* com.it.service.impl.*.*(..))")
    @Before("MyAdvice.MyPointcut()")
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("当前目标对象是: " + joinPoint.getTarget());
        System.out.println("表达式: " + joinPoint.getStaticPart());
        System.out.println("前置的增强..........");
    }

    // @AfterReturning("execution(* com.it.service.impl.*.*(..))")
    @AfterReturning("MyAdvice.MyPointcut()")
    public void afterReturningAdvice() {
        System.out.println("后置的增强..........");
    }
    .....
}

2.全注解开发

  • 使用注解代替xml文件中的以下内容
<!--    使用注解配置AOP,需要开启AOP自动代理-->
    <aop:aspectj-autoproxy/>

<!--    组件扫描-->
    <context:component-scan base-package="com.it"/>
  • 编写配置类SpringConfig
@Configuration
@ComponentScan("com.it") // <context:component-scan base-package="com.it"/>
@EnableAspectJAutoProxy // <aop:aspectj-autoproxy/>
public class SpringConfig {
}
  • 测试
@Test
public void test1(){
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = context.getBean(UserService.class);
    userService.show1();
}

image.png

3. 注解方式AOP原理剖析

之前在使用xml配置AOP时,是借助的Spring的外部命名空间的加载方式完成的,使用注解配置后,就抛弃了<aop:config>标签,而该标签最终加载了名为AspectJAwareAdvisorAutoProxyCreator的BeanPostProcessor ,最终,在该BeanPostProcessor中完成了代理对象的生成。

同样,从aspectj-autoproxy标签的解析器入手

image.png

<!--    使用注解配置AOP,需要开启AOP自动代理-->
    <aop:aspectj-autoproxy/>

同样,从aspectj-autoproxy标签的解析器入手

this.registerBeanDefinitionParser("aspectj-autoproxy", new
AspectJAutoProxyBeanDefinitionParser());    

而AspectJAutoProxyBeanDefinitionParser代码内部,最终也是执行了和xml方式AOP一样的代码

registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source)    

如果使用的是核心配置类的话

@Configuration
@ComponentScan("com.it") // <context:component-scan base-package="com.it"/>
@EnableAspectJAutoProxy // <aop:aspectj-autoproxy/>
public class SpringConfig {
}

查看@EnableAspectJAutoProxy源码,使用的也是@Import导入相关解析类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

使用@Import导入的AspectJAutoProxyRegistrar源码,一路追踪下去,最终还是注册了 AnnotationAwareAspectJAutoProxyCreator这个类

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
}

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, (Object) null);
}
    
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}    

image.png

④:基于AOP的声明式事务控制

1. Spring事务编程概述

事务是开发中必不可少的东西,使用JDBC开发时,我们使用connnection对事务进行控制,使用MyBatis时,我们使用SqlSession对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化,Spring 就将这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制 和声明式事务控制

image.png

2. 搭建测试环境

搭建一个转账的环境,dao层一个转出钱的方法,一个转入钱的方法,service层一个转账业务方法,内部分别调用dao层转出钱和转入钱的方法,准备工作如下

  • 数据库准备一个账户表t__account;

  • dao层准备一个AccountMapper,包括incrMoney和decrMoney两个方法;

  • service层准备—个transferMoney方法,分别调用incrMoney和decrMoney方法;

  • 在applicationContext文件中进行Bean的管理配置;

  • 测试正常转账与异常转账。

image.png

0.导入依赖\color{#00FF00}{0. 导入依赖}

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.7</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.23</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>
    
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>    

1.数据库准备一个账户表t_account;\color{#00FF00}{1. 数据库准备一个账户表t\_account;}

CREATE TABLE `tb_accouunt` (
  `id` bigint NOT NULL,
  `account_name` varchar(30) NOT NULL COMMENT '姓名',
  `money` bigint NOT NULL COMMENT '金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; 

image.png

2.dao层准备一个AccountMapper,包括incrMoneydecrMoney\color{#00FF00}{2. dao层准备一个AccountMapper,包括incrMoney和decrMoney两} 个方法;\color{#00FF00}{个方法;}

public interface AccountMapper {

    // 加钱
    void incrMoney(@Param("account_name")  String accountName,@Param("money") Long money);

    // 减钱
    void decrMoney(@Param("account_name")  String accountName,@Param("money") Long money);
}

3.service层准备—个transferMoney方法,分别调用incrMoney\color{#00FF00}{3. service层准备—个transferMoney方法,分别调用incrMoney和} decrMoney方法;\color{#00FF00}{decrMoney方法;}

  • AccountService
public interface AccountService {

    void transferMoney(String outAccount, String inAccount, Long money);
}
  • AccountServiceImpl
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    public void transferMoney(String outAccount, String inAccount, Long money) {
        accountMapper.decrMoney(outAccount,money);
        accountMapper.incrMoney(inAccount,money);
    }
}

4.applicationContext文件中进行Bean的管理配置;\color{#00FF00}{4. 在applicationContext文件中进行Bean的管理配置;}

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

<!--        组件扫描-->
    <context:component-scan base-package="com.it"/>
<!--    加载jdbc.properties文件-->
    <context:property-placeholder location="jdbc.properties"/>

<!--    配置数据源信息-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

<!--    配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到Spring容器-->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    配置MapperScannerConfigurer,作用扫描指定包,产生Mapper对象存储到Spring容器-->
    <bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.it.mapper"/>
    </bean>

</beans>

5.创建AccountMapper.xml文件路径与AccountMapper路径保持一致\color{#00FF00}{5. 创建AccountMapper.xml文件路径与AccountMapper路径保持一致}

  • AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.AccountMapper">

    <update id="incrMoney" parameterType="com.it.pojo.TbAccount">
        update tb_account
        set money = money + #{money}
        where account_name = #{account_name}
    </update>

    <update id="decrMoney">
        update tb_account
        set money = money - #{money}
        where account_name = #{account_name}
    </update>

</mapper>

6.测试正常转账与异常转账。\color{#00FF00}{6. 测试正常转账与异常转账。}

  • 正常转账

image.png

  • 异常转账

image.png

3. 基于xml声明事务控制

结合上面我们学习的AOP的技术,很容易就可以想到,可以使用AOP对Service的方法进行事务的增强。

  • 目标类:自定义的AccountServicelmpl,内部的方法是切点
  • 通知类:Spring提供的,通知方法已经定义好,只需要配置即可

我们分析:

  • 通知类是Spring提供的,需要导入Spring事务的相关的坐标;

  • 配置目标类AccountServicelmpl;

  • 使用advisor标签配置切面。

  • applicationContext.xml中引入tx标签坐标

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

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

<!--    配置Spring提供好的Advice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

<!--    事务增强的aop-->
    <aop:config>
<!--        配置切面表达式-->
        <aop:pointcut id="txPointcut" expression="execution(* com.it.service.impl.*.*(..))"/>
<!--        配置织入关系 通知advice-ref引入Spring提供好的-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
        
</beans>
  • 测试(转账异常)事务控制成功

image.png

4. 基于xml声明事务控制详解

image.png

isolation属性:指定事务的隔离级别,事务并发存在三大问题:脏读、不可重复读、幻读/虚读。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READ_COMMITTED和 REPEATABLE_READ

image.png


read-only属性:设置当前的只读状态,如果是查询则设置为true,可以提高查询性能,如果是更新(增删改)操作则设置为false

image.png


timeout属性:设置事务执行的超时时间,单位是秒,如果超过该时间限制但事务还没有完成,则自动回滚事务,不再继续执行。默认值是-1,即没有超时时间限制

image.png


propagation属性:设置事务的传播行为,主要解决是A方法调用B方法时,事务的传播方式问题的,例如:使用单方的事务,还是A和B都使用自己的事务等。事务的传播行为有如下七种属性值可配置

image.png


xml声明式事务控制原理 : www.bilibili.com/video/BV1rt…

5. 基于注解声明式事务控制

1.AccountServiceImpl上加注解\color{#00FF00}{1. AccountServiceImpl上加注解}

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public void transferMoney(String outAccount, String inAccount, Long money) {
        accountMapper.decrMoney(outAccount,money);
//        int i = 1/0;
        accountMapper.incrMoney(inAccount,money);
    }
}

2.applicationContext2.xml(修改xml配置)\color{#00FF00}{2. applicationContext2.xml(修改xml配置)}

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

<!--        组件扫描-->
  <context:component-scan base-package="com.it"/>
<!--    加载jdbc.properties文件-->
  <context:property-placeholder location="jdbc.properties"/>

<!--    配置数据源信息-->
  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <property name="driverClassName" value="${jdbc.driver}"/>
      <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
  </bean>

<!--    配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到Spring容器-->
  <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource"/>
  </bean>

<!--    配置MapperScannerConfigurer,作用扫描指定包,产生Mapper对象存储到Spring容器-->
  <bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="com.it.mapper"/>
  </bean>

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

<!--    事务的自动代理(注解驱动)-->
  <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

3.测试正常转账与异常转账。\color{#00FF00}{3. 测试正常转账与异常转账。}

  • 正常转账

image.png

  • 异常转账事务控制成功

image.png

6. 基于注解声明式事务控制(全注解)

1.创建SpringConfig类。\color{#00FF00}{1. 创建SpringConfig类。}

@Configuration
@ComponentScan("com.it")
@PropertySource("classpath:jdbc.properties")
@MapperScan(basePackages = "com.it.mapper")
// 事务的自动代理(注解驱动) <tx:annotation-driven transaction-manager="transactionManager"/>
@EnableTransactionManagement
public class SpringConfig {

    @Bean
    public DataSource dataSource(
           @Value("${jdbc.driver}") String driver,
           @Value("${jdbc.url}") String url,
           @Value("${jdbc.username}") String username,
           @Value("${jdbc.password}") String password
    ){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }


    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

2.测试正常转账与异常转账。\color{#00FF00}{2. 测试正常转账与异常转账。}

    @Test
    public void test1(){
//        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = context.getBean(AccountService.class);
        accountService.transferMoney("tom", "lucy", 500L);
    }
  • 正常转账

image.png

  • 异常转账事务控制成功

image.png

\color{#00FF00}{}