Spring和SpringMVC(三)

一 spring事务的操作

事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring 事务作用:在数据层或 业务层 保障一系列的数据库操作同成功同失败
数据层有事务我们可以理解,为什么业务层也需要处理事务呢 ?
举个简单的例子,
转账业务会有两次数据层的调用,一次是加钱一次是减钱
把事务放在数据层,加钱和减钱就有两个事务
没办法保证加钱和减钱同时成功或者同时失败
这个时候就需要将事务放在业务层进行处理。
Spring 为了管理事务,提供了一个平台事务管理器 PlatformTransactionManager
PlatformTransactionManager 只是一个接口, Spring 还为其提供了一个具体的实现
叫做 DataSourceTransactionManager。
从名称上可以看出,我们只需要给它一个 DataSource 对象,它就可以帮你去在业务层管理事务。其
内部采用的是 JDBC 的事务。所以说如果你持久层采用的是 JDBC 相关的技术,就可以采用这个事务管理
器来管理你的事务。而 Mybatis 内部采用的就是 JDBC 的事务,所以后期我们 Spring 整合 Mybatis
采用的这个 DataSourceTransactionManager 事务管理器

(一)核心步骤

第一步:
在要进行事务的总方法上面直接进行 加上@Transactional
第二步:
在jdbcConfig类中配置事务管理器:
@Bean
public PlatformTransactionManager transactionManager ( DataSource
dataSource ){
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager ();
transactionManager . setDataSource ( dataSource );
return transactionManager ;
}
}
第三步:开启事务注解:
@Configuration
@ComponentScan ( "com.blue" )
@PropertySource ( "classpath:jdbc.properties" )
@Import ({ JdbcConfig . class , MybatisConfig . class
// 开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
@EnableTransactionManagement 就是设置当前的Spring的环境中开启了注解事务支持
@Transactional 为当前的业务层添加了事务
这样在去运行整体的一个程序,就会是一个整体去提交或者回滚。

(二)事务管理员和事务协调员

事务管理员:发起事务方,在 Spring 中通常指代业务层开启事务的方法
事务协调员:加入事务方,在 Spring 中通常指代数据层方法,也可以是业务层方法

(三)事务的配置

readOnly true 只读事务, false 读写事务,增删改要设为 false, 查询设为 true
timeout: 设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚, -1 表示不设置超
时时间。
像这种的异常可能就不会回滚 IOException ();
rollbackFor: 当出现指定异常进行事务回滚
noRollbackFor: 当出现指定异常不进行事务回滚
思考 : 出现异常事务会自动回滚,这个是我们之前就已经知道的
noRollbackFor 是设定对于指定的异常不回滚,这个好理解 rollbackFor 是指定回滚异常,对于异常事务不应该都回滚么,为什么还要指定 ?

 
@Transactional ( rollbackFor = { IOException . class })

(四)事物的传播行为:

在一个事务中,不会因为一个异常的出现,而中断了整体的运行,也就是说在转账功能出现问题的基础上,单独开一个事务(日志的记录),并不会影响到这个事务的执行。
在事务传播的情况下,对要单独开事务操作的情况,加上propagation属性。
@Transactional ( propagation = Propagation . REQUIRES_NEW )
这样可以去实现,再转账的情况下,记录日志情况。这样日志的记录,不会因为异常的出现而中断了执行日志记录。

二 SpringAOP:

    (一)  简介:

        
前面我们在介绍 Spring 的时候说过, Spring 有两个核心的概念,一个是 IOC/DI ,一个是 AOP
前面已经对 IOC/DI 进行了系统的学习,接下来要学习它的另一个核心内容,就是 AOP
对于 AOP, 我们前面提过一句话是 : AOP 是在不改原有代码的前提下对其进行增强
AOP(Aspect Oriented Programming) 面向切面编程,一种编程范式,指导开发者如何组织程
序结构。
作用 : 在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代
理模式。
前面咱们有技术就可以实现这样的功能即 代理模式。
(1) 前面一直在强调, Spring AOP 是对一个类的方法在不进行任何修改的前提下实现增强。对于上
面的案例中 BookServiceImpl 中有 save , update , delete select 方法 , 这些方法我们给起了一
个名字叫 连接点
(2) BookServiceImpl 的四个方法中, update delete 只有打印没有计算万次执行消耗时间,
但是在运行的时候已经有该功能,那也就是说 update delete 方法都已经被增强,所以对于需要增
强的方法我们给起了一个名字叫 切入点
(3) 执行 BookServiceImpl update delete 方法的时候都被添加了一个计算万次执行消耗时间
的功能,将这个功能抽取到一个方法中,换句话说就是存放共性功能的方法,我们给起了个名字叫
(4) 通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添
加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们
给起了个名字叫 切面
(5) 通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也给起了个名字叫 通知类
至此 AOP 中的核心概念就已经介绍完了,总结下 :
连接点(JoinPoint) :程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
SpringAOP 中,理解为方法的执行
切入点(Pointcut): 匹配连接点的式子
SpringAOP 中,一个切入点可以描述一个具体方法,也可也匹配多个方法
一个具体的方法 : com.blue.dao 包下的 BookDao 接口中的无形参无返回值的 save
匹配多个方法 : 所有的 save 方法,所有的 get 开头的方法,所有以 Dao 结尾的接口中的任意
方法,所有带有一个参数的方法
连接点范围要比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法就不一
定要被增强,所以可能不是切入点。
通知(Advice): 在切入点处执行的操作,也就是共性功能 SpringAOP 中,功能最终以方法的形式呈现
通知类:定义通知的类
切面(Aspect): 描述通知与切入点的对应关系。
AOP实现的具体步骤:
1添加依赖:
<dependency>
<groupId> org.aspectj </groupId>
<artifactId> aspectjweaver </artifactId>
<version> 1.9.4 </version>
</dependency>
2定义接口和实现类
3定义通知类和通知: 通知就是将共性功能抽取出来后形成的方法,
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}

4定义切入点并且制作切面

切面是用来描述通知和切入点之间的关系,
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.blue.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}

这里的before就是通知会在切入点之前执行。

5开启注解格式的AOP功能:
@Configuration
@ComponentScan("com.blue")
@EnableAspectJAutoProxy
public class SpringConfig {
}

之后就是写一个程序进行执行。

小小的总结一下:

@EnableAspectJAutoProxy 开启注解格式AOP功能

@Aspect 设置当前类为AOP切面类

@Pointcut 设置切入点方法。

这里可以有一个小测试:
System . out . println ( bookDao );
System . out . println ( bookDao . getClass ());
同时打印出来这两个东西。
如果目标对象中的方法会被增强,那么容器中将存入的是目标对象的代理对象
如果目标对象中的方法不被增强,那么容器中将存入的是目标对象本身。
目标对象(Target) 原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终
工作的
代理(Proxy) 目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实
上面这两个概念比较抽象,简单来说,
目标对象就是要增强的类 [ :BookServiceImpl ] 对应的对象,也叫原始对象,不能说它不能运
行,只能说它在运行的过程中对于要增强的内容是缺失的。
SpringAOP 是在不改变原有设计 ( 代码 ) 的前提下对其进行增强的,它的底层采用的是代理模式实现
的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知
[:MyAdvice中的method方法]内容加进去,就实现了增强,这就是我们所说的代理(Proxy)

(二)切入点表达式:

  //切入点表达式:
//    @Pointcut("execution(void com.blue.dao.BookDao.update())")
//    @Pointcut("execution(void com.blue.dao.impl.BookDaoImpl.update())")
//    @Pointcut("execution(* com.blue.dao.impl.BookDaoImpl.update(*))")    //no
//    @Pointcut("execution(void com.*.*.*.update())")
//    @Pointcut("execution(* *..*(..))")
//    @Pointcut("execution(* *..*e(..))")
//    @Pointcut("execution(void com..*())")
//    @Pointcut("execution(* com.blue.*.*Service.find*(..))")
execution ( void com .blue . dao . BookDao . update ())
匹配接口,能匹配到
execution ( void com .blue . dao . impl . BookDaoImpl . update ())
匹配实现类,能匹配到
execution ( * com .blue . dao . impl . BookDaoImpl . update ())
返回值任意,能匹配到
execution ( * com .blue . dao . impl . BookDaoImpl . update ( * ))
返回值任意,但是 update 方法必须要有一个参数,无法匹配,要想匹配需要在 update 接口和实现类添加
参数
execution ( void com . * . * . * . * . update ())
返回值为 void , com 包下的任意包三层包下的任意类的 update 方法,匹配到的是实现类,能匹配
execution ( void com . * . * . * . update ())
返回值为 void , com 包下的任意两层包下的任意类的 update 方法,匹配到的是接口,能匹配
execution ( void * .. update ())
返回值为 void ,方法名是 update 的任意包下的任意类,能匹配
execution ( * * .. * (..))
匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution ( * * .. u * (..))
匹配项目中任意包任意类下只要以 u 开头的方法, update 方法能满足,能匹配
execution ( * * .. * e (..))
匹配项目中任意包任意类下只要以 e 结尾的方法, update save 方法能满足,能匹配
execution ( void com .. * ())
返回值为 void com 包下的任意包任意类任意方法,能匹配, * 代表的是方法
execution ( * com .blue . * . * Service . find * (..))
将项目中所有业务层方法的以 find 开头的方法匹配
execution ( * com .blue . * . * Service . save * (..))
将项目中所有业务层方法的以 save 开头的方法匹配
这里的切入点表达式再结合上后面的这个通知方式,比如环绕通知就可以扩展很多方法功能。
例如:
@Around("execution(* com.blue.controller.*.*(..))") 
*: 单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现.
匹配 com.blue 包下的任意包中的 UserService 类或接口中所有 find 开头的带有一个参数的
方法
.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

(三)五种通知方式:

@After 设置当天的通知在切入点之后进行执行。
@AtferReturning 设置当前通知方法在原始切入点方法,正常执行结束之后。
@AfterThrowing 设置当前通知方法在原始切点方云发生异常发生之后执行。
@Around 当前通知方法在原始的切入点方法前后执行。
@Before 当前通知方法在原切入的方法之前就执行。
1环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用,进而实现原始方法
调用前后同时添加通知
2. 通知中如果未使用 ProceedingJoinPoint 对原始方法进行调用将跳过原始方法的执行 3. 对原始方法的调用可以不接收返回值,通知方法设置成 void 即可,如果接收返回值,最好设定为
Object 类型
4. 原始方法的返回值如果是 void 类型,通知方法的返回值类型可以设置成 void, 也可以设置成
Object
5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理 Throwable 异常
就是抛出了throw Throwable 看方法名就能够看出来。
 //@Before:前置通知,在原始方法运行之前执行
    //@Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }

    //@After:后置通知,在原始方法运行之后执行
    @After("pt2()")
    public void after() {
        System.out.println("after advice ...");
    }

    //@Around:环绕通知,在原始方法运行的前后执行
//    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
    }

//    @Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        Integer ret = (Integer) pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
    }

    //@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象
//    @AfterReturning("pt2()")
    public void afterReturning() {
        System.out.println("afterReturning advice ...");
    }

    //@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行
   // @AfterThrowing("pt2()")
    public void afterThrowing() {
        System.out.println("afterThrowing 

这里需要注意的就是,如果说原始的方法有返回值,那么在这里用五种通知方式的时候,就需要进行返回响应的类型,这里就用object就可以。比较通用。
这样的话,就可以用AOP的思想进行很多的功能扩展。比如统计程序运行时间。
开始执行方法之前记录一个时间,执行方法,执行方法之后记录时间。

@Pointcut("execution(* com.blue.service.*Service.*(..))")
private void servicePt(){}



@Around("servicePt()")
public void runSpeed(ProceedingJoinPoint pjp){
Signature signature = pjp.getSignature();
//通过签名获取执行操作名称(接口名)
String className = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +
(end-start) + "ms");
}
JoinPoint :适用于前置、后置、返回后、抛出异常后通知
ProceedingJoinPoint :适用于环绕通知.
这里注意一下:
如果说这里接受的参数是很多的,就可以用数组来进行实现。
public Object around ( ProceedingJoinPoint pjp ) throws Throwable {
Object [] args = pjp . getArgs ();
System . out . println ( Arrays . toString ( args ));
args [ 0 ] = 666 ;
Object ret = pjp . proceed ( args );
return ret ;
}

总结一下AOP的概念:

概念: AOP(Aspect Oriented Programming) 面向切面编程,一种编程范式
作用:在不惊动原始设计的基础上为方法进行功能 增强
核心概念
代理( Proxy ): SpringAOP 的核心本质是采用代理模式实现的
连接点( JoinPoint ):在 SpringAOP 中,理解为任意方法的执行
切入点( Pointcut ):匹配连接点的式子,也是具有共性功能的方法描述
通知( Advice ):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
切面( Aspect ):描述通知与切入点的对应关系
目标对象( Target ):被代理的原始对象成为目标对象
切入点表达式
execution(* com.blue.service.*Service.*(..))
五种通知类型
前置通知
后置通知
环绕通知(重点)
环绕通知依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用
环绕通知可以隔离原始方法的调用执行
环绕通知返回值设置为 Object 类型
环绕通知中可以对原始方法调用过程中出现的异常进行处理
返回后通知
抛出异常后通知
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值