spring(三)

实现事务代码重用

思路 :

    1) 提供了一个代理类(Proxy)
       调用通知类的invoke方法
       获取方法对象和方法实际参数
       与目标要实现相同的接口(目的是让使用者察觉不出是代理替换了原来的目标)

    2) 提供了一个通知类(Advice)
        实现了重复代码(事务的重复代码)
        反射调用了目标对象的方法
        把重复代码和目标方法联系在了一起

  jdk提供了一个通用的接口 InvocationHandler (通知接口)

 

 

动态代理

自己实现的代理类称之为‘静态代理’
jdk提供了动态代理技术(程序运行期间,由jdk生成这个代理类和它的实例对象)

 正常使用类:*.java -> javac -> *.class -> java -> 加载该class到虚拟机
动态代理 直接生成了*.class字节码, 加载该class到虚拟机

Proxy.newProxyInstance(类加载器, 要实现的接口数组, InvocationHandler);

jdk的动态代理, 只能针对接口实现代理, jdk动态代理在高版本中性能优于cglib 
 cglib动态代理, 既可以针对接口实现代理,也可以生成子类充当目标父类的代理

 

 

SpringAOP

AOP:面向切面编程(aspect oriented programming),在程序开发中主
           要用来解决一些系统层面上的问题,比如日志,事务,权限等待

技术概念上理解:面向切面编程就是把重复的逻辑抽取为一个通知方法, 
                             然后通过切点匹配来决定哪些目标要应用这些通知。
                             其中利用了代理的技术,在代理中检查切点是否匹配,
                             调用通知(多个),最后调用目标


一:AOP的基本概念

(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知

(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用

(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式(定义匹配规则)

(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

 

二:基于注解的AOP配置方式

1. pom.xml 新增依赖

<!-- spring aop -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
	<version>4.3.17.RELEASE</version>
</dependency>

<!-- 第三方 aop依赖 aspectJ -->
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.8.9</version>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjrt</artifactId>
	<version>1.8.9</version>
</dependency>

 

2. spring的配置文件加入aop相关标签

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

	<!-- 目标类 -->
    <bean id="userService" class="spring.service.UserServiceTarget">
    </bean>

    <!-- 通知类 -->
    <bean id="transactionAdvice" class="spring.advice.TransactionAdvice">
    </bean>

    <!-- 让相关注解生效, 还能够帮我们生成底层的代理对象 -->
    <aop:aspectj-autoproxy/>
</beans>

 

3. 通知类型介绍

环绕通知: @Around(切点表达式) 加在通知方法上(功能最全的), 通知方法环绕着目标方法
前置通知: @Before  通知代码只会出现在目标方法之前
正常结束通知: @AfterReturning在目标方法正常完成后做增强,除了指定切入点表达式后,还
                        可以指定一个返回值形参名returning,代表目标方法的返回值
异常通知: @AfterThrowing 主要用来处理程序中未处理的异常,除了指定切入点表达式后,还
                 可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出
                 的异常对象
结束通知: @After 在目标方法结束后(正常或异常),总会调用的通知

 

4. 切点表达式

 * within(包名.类名) 这个类中所有方法执行时,都会应用通知方法
 * execution(访问修饰符 返回值类型 包名.类名.方法名( 参数类型... ))
    注意`*` 可以匹配任意类型, 可以出现在方法返回值,方法名,类名当中
    注意`..` 可以匹配方法参数,表示参数的个数和类型都是任意的
 * @annotation(包名.注解名)
    它是根据方法上的注解进行匹配, 如果有注解则匹配

@Aspect
public class Advice {

    @Before("within(spring.service.UserServiceTarget)")
    public void before() {
        System.out.println("==== 在目标方法之前被调用");

    }

    @AfterReturning("within(spring.service.UserServiceTarget)")
    public void afterReturning() {
        System.out.println("======== 方法正常结束");
    }

    @AfterThrowing(pointcut = "within(spring.service.UserServiceTarget)", throwing = "e")
    public void exception(Exception e) { // 类似于catch ,可以获取异常信息
        System.out.println("=====出现了异常" + e.getMessage());
    }

    @After("within(spring.service.UserServiceTarget)")
    public void after() {
        System.out.println("==========总会被调用");
    }

}

 

注意:Arround环绕通知需要引入ProceedingJoinPoint
          
且Around不要和AfterThrowing同时使用,会冲突

/ 切面
@Aspect
public class TransactionAdvice {

//    @Around("within(spring.service.UserServiceTarget)")

//    @Around("execution(public * spring.service.UserServiceTarget.biz1(..) )")

    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    // 通过切点表达式,把通知和目标结合在一起
    // 作用当UserServiceTarget类中任意一个方法执行时(biz1,biz2) 就会和下面的transaction方法结合在一起了


    // 通知类型:环绕通知,决定了这个通知方法会和哪些目标方法结合
    // 这个方法称为通知方法,它其中包含了一些重复逻辑,另外它负责调用目标方法
    public Object transaction(ProceedingJoinPoint pjp) { // pjp 内部去调用目标的方法
        System.out.println("开始事务");
        Object r = null;
        try {
            // 调用目标
            r = pjp.proceed(); // 目标  biz1,  biz2...

            System.out.println("提交事务");
        } catch (Throwable e) {
            System.out.println("回滚事务");
        }
        return r;
    }
}

 

5. 调用流程

容器启动时:
       1)spring会检查容器,看是否需要为这些bean创建代理对象
       2)检查所有的切点表达式,如果匹配到了目标,就为该目标创建代理对象

获取对象时(getBean, 依赖注入):
            会检查这个类型是否有代理对象,如果有,优先返回代理对象
 
调用方法时:
            调用了代理对象的方法,代理对象会首先经过通知类(多个)
 
            检查切点,如果切点匹配,再进行下面的通知调用
 
            根据不同的通知类型进行调用:
                例如前置通知先调通知,再调目标
                如果是环绕通知,先调用通知,通知内部调用目标 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值