spring的AOP和事务

动态代理

代理模式:给一个对象提供一个代理,并由代理对象来控制真实对象的访问(调用者并不知道真实对象是什么)。
代理模式分静态代理和动态代理。这里只讨论动态代理,通俗的讲,动态代理就是在不修改代码的基础对被代理对象进行方法的加强html

基于接口的动态代理

JDK自带的动态代理就是基于接口的动态代理,被代理对象至少要实现一个接口,不然就没法使用代理。底层仍是基于Java的反射来建立代理对象的。
JDK动态代理主要涉及两个类,java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler 。下面来看个例子。java

被代理类的接口IProductspring

public interface IProduct {
    void saleProduct(Float money);
}
复制代码

被代理类Product实现上面这个接口数据库

public class Product implements IProduct{
    @Override
    public void saleProduct(Float money) {
        System.out.println("卖出一个产品,收到金额:" + money);
    }
}
复制代码

编写一个客户端类获取一个Product的动态代理的对象,来控制被代理对象的访问。 InvocationHandler的实现主要经过内部类实现(能够直接使用lambda表达式更简洁)。编程

public class Client {
    public static void main(String[] args){
        Product product = new Product();
        // 基于接口的动态代理(jdk自带的)
        // proxyProduct就是Proxy.newProxyInstance生成的代理对象,须要强转为被代理对象的接口类。
        // 参数: 
        // ClassLoader:代理对象的类加载器。使用和被代理对象相同的类加载器,直接调用getClass().getClassLoader()获取。
        // Class<?>[]:被代理对象的接口的字节码。直接调用getClass().getInterfaces()获取就行
        // InvocationHandler:进行代码的加强。直接使用内部类或者new一个InvocationHandler的实现类(在实现类中进行代码加强)
        IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader()
                , product.getClass().getInterfaces(), new InvocationHandler() {
                    /** * 进行代理加强的方法 * 参数: * proxy:当前的代理对象 * method:被代理对象当前执行的方法 * args:被代理对象当前执行的方法的参数 */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object res = null;
                        // 获取方法执行的参数
                        Float money = (Float)args[0];
                        // 判断当前方法是不是销售方法
                        if ("saleProduct".equals(method.getName())){
                            // 返回值与被代理对象方法的相同
                            res = method.invoke(product, money * 0.8f);
                        }
                        return res;
                    }
                });
        // 调用这个代理对象,执行被代理对象的方法。
        proxyProduct.saleProduct(1000f);
    }
}
复制代码

基于子类的动态代理

CGLIB动态代理是一个基于ASM字节码操做框架的代码生成库,它被普遍用于基于代理的AOP框架提供方法拦截。
本质上说,对于须要被代理的类,它只是动态的生成一个子类以覆盖非final的方法(因此它不能代理final类或final方法),同时绑定钩子回调自定义的拦截器。它比使用Java反射的JDK动态代理方法更快。
CGLIB动态代理的核心是net.sf.cglib.proxy.Enhancer类(用于建立被代理对象子类),net.sf.cglib.proxy.MethodInterceptor是最经常使用的回调类型(它是net.sf.cglib.proxy.Callback的一个子接口)。
使用前须要引入CGLIB的jar依赖。数组

<!--cglib动态代理的依赖-->
<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.2.12</version>
</dependency>
复制代码

下面来看一个示例(被代理对象仍是和上面的相同,这里实现一个客户端类)。springboot

public class Client {
    public static void main(String[] args){
        Product product = new Product();
        /** * 基于子类的动态代理(cglib) * Enhancer的create方法,经过Enhancer来建立代理对象,MethodInterceptor来加强方法 * 参数: * Class:被代理对象的字节码 * Callback:用于加强方法。通常使用这个接口的子接口MethodInterceptor。 */
        Product proxyProduct =  (Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
            /** * 执行被代理对象的任何方法都会通过此方法 * @param proxy 当前代理对象 * @param method 被代理对象要执行的方法 * @param args1 要执行的方法的参数 * 以上三个参数与基于接口的动态代理(jdk)的参数一致 * @param methodProxy 当前执行方法的代理对象 通常使用这个对象来调用原始对象的方法,由于它性能更高 * @return 与被代理对象要执行的方法的返回值一致 * @throws Throwable */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args1, MethodProxy methodProxy) throws Throwable {
                Object res = null;
                // 获取方法执行的参数
                Float money = (Float)args1[0];
                // 判断当前方法是不是销售方法
                if ("saleProduct".equals(method.getName())){
                    // 修改原始方法的参数
                    args1[0] = money * 0.8f;
                    // 与JDK动态代理的一个区别,使用MethodProxy对象来调用原始对象的方法
                    res = methodProxy.invoke(product, args1);
                }
                return res;
            }
        });
        // 经过代理对象执行被代理对象的方法
        proxyProduct.saleProduct(1000f);
    }
}
复制代码

spring AOP

spring AOP是基于动态代理实现的,spring默认使用JDK动态代理实现AOP,也能够强制使用CGLIB。AOP是IoC的一种补充,IoC不依赖与AOP,可是AOP依赖与IoC。mybatis

AOP的一些概念

  • JoinPoint(链接点):指被拦截到的点。在spring AOP中,就是指那些被拦截的方法。
  • PointCut(切入点):是指筛选出的链接点(由于全部的切入点都是链接点,可是有的链接点并不须要进行拦截加强方法)
  • Advice(通知/加强):须要完成的工做叫作通知,就是写的业务逻辑代码中的公共代码,好比事务、日志等。通知的类型有五种。
    • Before(前置通知):在链接点(拦截的方法)执行前的通知。
    • AfterReturning(后置通知):在链接点正常完成返回后的通知。
    • AfterThrowing(异常通知):若是方法执行过程当中抛出了异常,则执行此通知。
    • After(最终通知):不管方法执行是否成功,最终都要执行这个通知。
    • Around(环绕通知):围绕链接点的通知,它是最经常使用的通知,由于它能够包括以上四种通知。
  • Aspect(切面):其实就是通知和切点的结合,通知和切点共同定义了切面的所有内容,它是干什么的,何时在哪执行。
  • Introduction(引入):在不改变一个现有类代码的状况下,为该类添加属性和方法,能够在无需修改现有类的前提下,让它们具备新的行为和状态。
  • Target(目标):被通知的对象。也就是须要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。
  • Weaving(织入):切面在指定的链接点被织入到目标对象中,在目标对象的生命周期里有多个点(编译期,类加载期,运行期)能够进行织入,spring是在运行期织入的。

@Aspectj支持

Spring 使用 AspectJ 提供的 library 解释与 AspectJ 5 相同的注释,用于切入点解析和匹配。可是,AOP 运行时仍然是纯粹的 Spring AOP,而且不依赖于 AspectJ 编译器或编织器。(也就是spring只是利用Aspectj来对切入点进行解析和匹配,真正的AOP的执行仍是使用Spring AOP)。app

在springboot中使用spring AOP很是的方便,只需引入一个spring支持AspectJ的starter依赖便可。框架

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
复制代码

在spring中也很简单,在引用spring AOP的基础上再引入一个aspectjweaver依赖就行。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>
复制代码

在spring中,要使用 Java @Configuration启用 @AspectJ 支持,还需添加@EnableAspectJAutoProxy注解(在springboot中不用)。

下面来看一个简单的示例(基于springboot)。
先写个Service类(包括接口和实现类)。

public interface AccountService {
    Account findById(Integer aid);
}

@Service
public class AccountServiceImpl implements AccountService {
    private static final Logger log = LoggerFactory.getLogger(AccountServiceImpl.class);
    @Override
    public Account findById(Integer aid) {
        log.info("正在执行方法");
        return accountMapper.findById(aid);
    }
}
复制代码

一个配置类,对service中的方法进行切面。

@Aspect // 声明切面, 这个bean将被spring自动检测,并用于配置Spring AOP
@Component // 将这个类注册为一个bean,交给spring来管理(因此spring AOP依赖于IoC)
public class Logger {

    private static org.slf4j.Logger log = LoggerFactory.getLogger(Logger.class);

    /** * 通用化切入点表达式,其余切入点能够直接引用这个 */
    @Pointcut("execution(* tkmybatis.service.impl.*.*(..))")
    public void pointCut(){
    }

    /** * 前置通知,在切入点方法执行以前执行 */
    @Before("pointCut()")
    public void printLogBefore(){
        log.info("我在方法执行前");
    }

    /** * 后置通知,方法正常返回后执行 */
    @AfterReturning("pointCut()")
    public void printLogAfterReturning(){
        log.info("我在方法执行正常返回后");
    }

    /** * 异常通知,方法发生异常时执行 */
    @AfterThrowing("pointCut()")
    public void printLogAfterThrow(){
        log.info("我在方法执行抛出异常后");
    }

    /** * 最终通知,不管方法是否执行正常,它都会在最后执行 */
    @After("pointCut()")
    public void printLogAfter(){
        log.info("我在方法执行最后");
    }

    @Around("pointCut()")
    public Object printAround(ProceedingJoinPoint pjp){
        Object res;
        try{
            Object[] args = pjp.getArgs();
            log.info("我在方法执行前");
            res = pjp.proceed(args);
            log.info("我在方法执行正常返回后2");
            return res;
        }catch (Throwable t){
            log.info("我在方法执行抛出异常后2");
            throw new RuntimeException(t);
        }finally {
            log.info("我在方法执行最后");
        }
    }
}
复制代码

对于切入点表达式execution(* tkmybatis.service.impl.*.*(..)) ,这里作一下解释。
execution表达式原本有三个部分(可见类型 返回值 链接点的全限定名),这里的可见类型能够省略。

  • *表明返回值能够为任何值。
  • tkmybatis.service.impl表明service实现类所在的包(进一步还能够直接用* .来替代,表示全部包)。
  • .* 表明这个包下的全部类。下一个.* 表明这个类下的全部方法(还能够用方法前缀来表示,好比find*)。
  • 括号里的 .. 表明参数能够为任意参数,可为多个或无。

更多高级用法详见Spring官方文档 Spring AOP

Spring 事务管理

Spring支持声明式的事务管理和编程式的事务管理。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就能够得到彻底的事务支持。Spring推荐使用声明式的事务管理。
而声明式的事务管理就是基于Spring AOP的,其本质就是对要执行的方法进行拦截,在方法执行前加入一个事务,方法执行完成后根据状况判断是提交事务仍是回滚事务(抛出了异常)。

事务的隔离级别

经过org.springframework.transaction.annotation.Isolation 这个枚举类来指定。

public enum Isolation {
    // 这是默认值,表示使用底层数据库的默认隔离级别。对于MySQL的InnoDB存储引擎,它是REPEATABLE_READ,其它通常的都是READ_COMMITTED
    DEFAULT(-1),
    // 跟没有同样,几乎不使用。
    READ_UNCOMMITTED(1),
    // 只能读取另外一个事务已提交的事务,能防止脏读。也是通常数据库的默认隔离级别。
    READ_COMMITTED(2),
    // 可重复读(在一个事务内屡次查询的结果相同,其它事务不可修改该查询条件范围内的数据,至关于加了个读锁)
    REPEATABLE_READ(4),
    // 全部的事务依次逐个执行,至关于串行化了,效率过低,通常也不使用。
    SERIALIZABLE(8);
}
复制代码

事务的传播行为

若是在开始当前事务以前,一个事务上下文已经存在,此时有若干选项能够指定一个事务性方法的执行行为。org.springframework.transaction.annotation.Propagation枚举类中定义了7表示传播行为的枚举值。

public enum Propagation {

    // 若是当前存在事务,则加入该事务;若是当前没有事务,则建立一个新的事务。
    REQUIRED(0),
    // 若是当前存在事务,则加入该事务;若是当前没有事务,则以非事务的方式继续运行。
    SUPPORTS(1),
    // 若是当前存在事务,则加入该事务;若是当前没有事务,则抛出异常。
    MANDATORY(2),
    // 建立一个新的事务,若是当前存在事务,则把当前事务挂起。
    REQUIRES_NEW(3),
    // 以非事务方式运行,若是当前存在事务,则把当前事务挂起。
    NOT_SUPPORTED(4),
    // 以非事务方式运行,若是当前存在事务,则抛出异常。
    NEVER(5),
    // 若是当前存在事务,则建立一个事务做为当前事务的嵌套事务来运行;若是当前没有事务,则该取值等价于REQUIRED。
    NESTED(6);
}
复制代码

声明式事务的使用

声明式事务能够直接使用@Transactional注解(这里只讨论这个)来配置,固然也可使用XML配置文件。
先来看一下@Transactional注解都有啥

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    // 别名为transactionManager,其实这两是同一个。就是事务的名字。
    @AliasFor("transactionManager")
	String value() default "";
    // 事务的传播行为,默认值为REQUIRED
    Propagation propagation() default Propagation.REQUIRED;
    // 事务的隔离级别,默认为默认值(也就是使用底层数据库的隔离级别)
    Isolation isolation() default Isolation.DEFAULT;
    // 超时时间,默认为 -1,也就是没有超时时间。
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    // 是否只读,默认为false。
    boolean readOnly() default false;
    // 会触发事务回滚的异常的字节码数组
    Class<? extends Throwable>[] rollbackFor() default {};
    // 不会触发事务回滚的异常的字节码数组
    Class<? extends Throwable>[] noRollbackFor() default {};
}
复制代码

这里直接在实现类上使用,这个注解还能够在接口(通常不在接口上使用)和方法上使用(可见性必须为public,不然会被忽略)。
若是是在spring中使用,须要在配置类中加上@EnableTransactionManagement注解(springboot则不须要)。proxy-target-class属性能够控制动态代理的类型,若是值为false或忽略此属性,则使用JDK的动态代理,能够设置为true以强制使用CGLIB来进行动态代理(若是被代理的类没有实现接口,将会自动使用CGLIB进行动态代理)。

@Service
@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class) // 这里触发回滚的异常设置为Exception
public class AccountServiceImpl implements AccountService {
}
复制代码

参考:
CGLIB动态代理介绍
Spring AOP
Spring AOP的使用
Spring 事务管理
Spring 事务

相关文章
相关标签/搜索