全栈式框架-Spring(下)


前言

Spring是分层的JavaSE/EE full-stack 轻量级开源框架,以IOC(Inverse of Control 控制反转)和AOP(Aspect Oriented Programming 面向切面编程)为内核,使用基本的JavaBean来实现程序功能。


一、AOP

1.1问题的提出

在实际开发中,可能会同时需要编写业务核心代码、事务处理代码和日志记录代码,通常的做法如下

public class Myservice {
    public void logic(){
        doLog();
        //处理业务核心代码
        doTransaction();
    }
    public void doLog(){
        //日志代码
    }
    public void doTransaction(){
        //事务代码
    }
}

这种将核心业务模块与其他非核心的代码交织在一起,不利于代码的维护,影响了代码的模块独立性能。实际上,事务处理和日志记录可以独立在一个模块里,可给所有的服务公用,因此,面向切面的编程技术AOP便应运而生。


1.2AOP 概述

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP和OOP有什么区别?
OOP:面向对象编程:针对业务处理过程的实体及其属性和行为进行抽象封装,以获 得更加清晰高效的逻辑单元划分
AOP:AOP 则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果

例:
对于学生这样一个业务实体进行封装,自然是OOP的任务,我们可以建立一个Student类,并将学生相关的属性和行为封装其中。而用AOP 设计思想对学生进行封装则无从谈起。
同样,对于保存日志、权限检查、事务处理这些动作片段进行划分,是AOP的目标领域。

总结:OOP面向名次领域,AOP面向动词领域。
在这里插入图片描述
面向切面编程的好处就是: 减少重复,专注业务
核心原理: 使用动态代理的方式在执行方法前后或者出现异常的时候做加入相关的逻辑


1.3AOP基本概念

1.连接点(Joinpoint):类中可以被增强的方法,这个方法就被称为连接点
2.切入点(pointcut):类中有很多方法可以被增强,但实际中只有 add 和 update被增了,那么 add 和 update 方法就被称为切入点(实际实现的连接点)
3.通知(Advice): 通知是指一个切面在特定的连接点要做的事情(增强的功能)。通 知分为方法执行前通知,方法执行后通知,环绕通知等.
4.切面(Aspect):把通知添加到切入点的过程叫切面.
5.目标(Target): 代理的目标对象(要增强的类)
6.代理(Proxy): 向目标对象应用通知之后创建的代理对象


二、springAOP实现

在Spring中使用AOP开发时,一般使用 AspectJ的实现方式。 AspectJ是一个优秀面向切面的框架,它扩展了Java语言,提供了强大的切面实现。

AspectJ 中常用的通知有五种类型:
1.前置通知:方法执行之前
2.后置通知:在方法执行之后执行
3.环绕通知:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。
4.异常通知:在方法抛出异常之后
5.最终通知:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。

2.1基于aspectj 的 xml配置方式实现

2.1.1下载 AOP 相关 jar

<dependency> 
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId> 
	<version>5.2.2.RELEASE</version> 
</dependency>

2.1.2在xml文件中配置


    <bean id="aopDemo" class="com.ffyc.spring.aop.AopDemo"></bean>

    <aop:config>
        <!--配置切入点-->
        <!--execution(* com.ffyc.spring.dao.*.*(..))       该表达式的意思是匹配com.ffyc.spring.dao中任意类的任意方法的执行-->
        <!-- 返回类型       包名            任意参数-->
        <aop:pointcut expression="execution(* com.ffyc.spring.dao.UserDao.save2(..))" id="save2"></aop:pointcut>
        
        <!--编织:配置通知和切入点,把两者连接起来-->
        <aop:aspect ref="aopDemo">
            <!-- <aop:before method="saveLog" pointcut-ref="save2"/>-->
            <!--配置环绕通知,可以用环绕通知一次性完成前置后置和异常通知-->
            <aop:around method="saveLogRound" pointcut-ref="save2"/>
        </aop:aspect>
    </aop:config>

在这里插入图片描述

2.2基于注解方式的实现

2.2.1启动 AspectJ 支持

    <!--启动 AspectJ 支持-->
    <aop:aspectj-autoproxy />

2.2.2定义通知

@Component
@Aspect
public class AopDemo {

    public void saveLog(){
        System.out.println("保存日志");
    }
    /*环绕通知:当调用配置了环绕通知的方法时,先会进通知的方法,执行前置通知,当调用ProceedingJoinPoint类的processd()方法会进入到
        实际被增强的方法体中,如果出现异常,则执行异常通知,最后执行后置通知*/

    @Around("execution(* com.ffyc.spring.dao.UserDao.save2(..))")
    public void saveLogRound(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("前置通知");
        pj.proceed();
        System.out.println("后置通知");
    }

}

2.2.3 测试

@Repository
public class UserDao {
    public void save2(){
        System.out.println("被增强的方法体执行了");
    }
}

@Service()
public class UserService {
   @Autowired
   @Qualifier("userDao" )
   private UserDao userDao;
   
   public UserDao getUserDao() {
        return userDao;
    }
}
public class Test1 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService=applicationContext.getBean("userService",UserService.class);
        userService.getUserDao().save2();
    }
}

在这里插入图片描述


三、事物管理

3.1 事务的相关概念

事物可以看做是由对数据库若干操作组成的一个单元。

理解事务之前,先举一个日常生活中最常见的事:转账。
比如你要给你好朋友转账500元,大体有两个步骤:首先你的银行卡扣掉500元钱;然后你好朋友的银行卡多500元钱。这两个步骤必须是要么都执行要么都不执行。不管哪一个步骤失败了以后,整个转账过程都能回滚。

事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,从而保证数据满足一致性的要求。

3.2 Spring中的事务管理

3.2.1编程式事务

在项目中很少使 用 , 这种方式需要注入一个事务管理对象 TransactionTemplate ,然后在我们代码中需要提交事务或回滚事务时自己写代码实现。

3.2.1声明式事务

建立在AOP基础上,本质是对方法前后进行拦截,所以声明式事务是方法级别的。
Spring针对不同的dao框架,提供了不同的实现类,Jdbc,mybatis事物管理实现类是 DataSourceTransactionManager.

1.配置事物管理器

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driverClass}"></property>
        <property name="url" value="${jdbcUrl}"></property>
        <property name="username" value="${user}"></property>
        <property name="password" value="${pwd}"></property>
        <property name="initialSize" value="10"></property>
        <property name="maxActive" value="20"></property>
    </bean>

    <!-- 配置 spring 事务管理类, 并注入数据源 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

2.Xml配置方式

    <tx:advice id="txadvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <!--配置切入点-->
        <aop:pointcut expression="execution(* com.ffyc.spring.dao.UserDao.save2(..))" id="allmethod"/>
        <!--把切入点和配置传播行为编织起来-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="allmethod"/>
    </aop:config>

测试

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService=applicationContext.getBean("userService",UserService.class);
        userService.getUserDao().save2();
    }
}

UserDao中的save2方法

public void save2(){
        jdbcTemplate.update("INSERT INTO admin(NAME,sex,phone)VALUES(?,?,?)", "李军", "男", "15757900521");
        int x=10/0;
        jdbcTemplate.update("INSERT INTO admin(NAME,sex,phone)VALUES(?,?,?)", "王刚", "男", "15257909876");
    }

在这里插入图片描述
save2()方法中出现了除0异常,由于我们为save2()方法加了事务管理,所以数据库中应该没有插入李军和王刚的信息
在这里插入图片描述
当我们把异常取消,重新测试一下代码,看看数据能不能正常插入

    public void save2(){
        jdbcTemplate.update("INSERT INTO admin(NAME,sex,phone)VALUES(?,?,?)", "李军", "男", "13057900521");
       // int x=10/0;
        jdbcTemplate.update("INSERT INTO admin(NAME,sex,phone)VALUES(?,?,?)", "王刚", "男", "13057900521");
    }

在这里插入图片描述
从数据库中可以看出两条数据插入到了数据库中

3.注解方式
xml的配置相对来说比较繁琐,不方便使用,spring实现事务管理还有一种注解方式

开启注解事务管理

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

在需要事务管理的方法或者类(把注解加到类上面,该类中的所有方法都添加事务管理)上面添加 @Transactional注解即可

    @Transactional(propagation= Propagation.REQUIRED)
    public void save2(){
        jdbcTemplate.update("INSERT INTO admin(NAME,sex,phone)VALUES(?,?,?)", "李军", "男", "13057900521");
       // int x=10/0;
        jdbcTemplate.update("INSERT INTO admin(NAME,sex,phone)VALUES(?,?,?)", "王刚", "男", "13057900521");
    }

四、事务传播行为

4.1什么叫事务传播行为?

即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。

事务传播行为当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

注意:事务传播行为是 Spring 框架独有的事务增强特性,他不属于的事务实际提供方数据库行为

例如:A事务方法调用B事务方法时,B是在A事务方法中运行还是由自己开启一个方法运行,这就是由事务传播行为决定

4.2事务传播行为类型

4.2.1 PROPAGATION.REQUIRED
在这里插入图片描述

4.2.2 PROPAGATION.SUPPORTS
在这里插入图片描述
4.2.3 PROPAGATION.REQUIRES_NEW
在这里插入图片描述

4.3测试事务传播行为

1.PROPAGATION.REQUIRED

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        DeptService deptService=applicationContext.getBean("DeptService",DeptService.class);
        deptService.saveDept();
    }
}
@Service(value = "DeptService")
public class DeptService {
    @Autowired
    DeptDao  deptDao;
    @Autowired
    CommonService commonService;


    @Transactional(propagation= Propagation.REQUIRED)
    public void saveDept(){
        deptDao.saveDept();
        commonService.saveLog();
    }
}
@Service
public class CommonService {
    @Autowired
    CommonDao commonDao;

    @Transactional(propagation=Propagation.REQUIRED)
    public void saveLog(){
        commonDao.saveLog();
        int a=10/0;
    }
}

在这里插入图片描述
因为CommonService的事务传播行为为Propagation.REQUIRES_NEW,所以CommonService加入到了DeptService事务中,当CommonService中的saveLog()方法出现异常时,两个方法都不会提交数据到数据库中


2.PROPAGATION.REQUIRES_NEW

这次测试我们把异常加到调用者上面

 @Transactional(propagation= Propagation.REQUIRED)
    public void saveDept(){
        deptDao.saveDept();
        commonService.saveLog();
        int a=10/0;
    }

然后修改被调用者的事务管理为PROPAGATION.REQUIRES_NEW

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void saveLog(){
        commonDao.saveLog();
    }

在这里插入图片描述
因为CommonService的事务传播行为为Propagation.REQUIRES_NEW,所以CommonService把DeptService事务挂起,自己新建了一个事务,由于该事务中没有异常,所以 commonDao.saveLog()会提交到数据库中,但DeptService中的saveDept()方法出现了异常,所以deptDao.saveDept()不会提交到数据库中

注意:
事务不生效的场景:
1.异常被 catch 捕获导致@Transactional 失效
2.@Transactional 应用在非 public 修饰的方法上

本文主要介绍了三种事务传播类型,关于其他的事务传播类型,读者可以自行查阅相关资料了解


五、Spring 集成 Mybatis

其核心是将 SqlSessionFactory 交由 Spring 管理,并由 Spring管理对dao接口的代理实现。

5.1导入mybatis jar 包

<!--Spring 结合 mybatis 插件包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>

5.2配置 sqlSessionFactory

<!--配置sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis.xml"></property>
        <property name="mapperLocations" value="classpath:mapper/*Mapper.xml"></property>
    </bean>

5.3指定生成接口代理

<!-- 生成接口代理对象-->
    <bean id="mapperFactory" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.ffyc.ssm.dao"></property>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

在 service 中注入Dao代理接口,此接口由spring代理实现
在这里插入图片描述


总结

本章主要介绍了spring的AOP的实现,事务管理以及几种事务传播行为。AOP是spring的内核,读者不仅要掌握AOP的实现,更应该在深入学习AOP的原理,扩展自己的思维。在以后的学习中,我们将利用SpringBoot整合spring、mybatis以及springMVC,进一步提高我们的开发效率。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JinziH Never Give Up

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值