Spring (2) AOP

1 代理(Proxy)模式

        一种设计模式,它的作用是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接调用,而是通过代理类间接调用

1.1 静态代理

        代理类持有目标对象,并且实现相同的接口,代理类在目标方法调用前后进行额外的操作,代理类和目标对象的关系是在编译期写死的

        静态代理工作原理:

                (1) 定义一个接口(或抽象类),目标对象实现这个接口

                (2) 创建一个代理类,实现目标接口,并持有目标对象

                (3) 代理类重写目标接口方法,在重写方法中调用目标对象同名方法

                (4) 代理类的重写方法在目标对象同名方法前后做额外操作

        示例代码:

// 接口
public interface Travel {
    void vacation();
}

// 目标对象
public class TravelImpl implements Travel{
    @Override
    public void vacation() {
        System.out.println("度假...");
    }
}

// 代理类
public class TravelProxy implements Travel {

    private TravelImpl travel;  //引入目标对象

    public void setTravel(TravelImpl travel) {
        this.travel = travel;
    }

    @Override
    public void vacation() {
        System.out.println("去的车票购买");
        travel.vacation();
        System.out.println("回的车票购买");
    }
}

// 测试
@Test
public void travelTest() throws Exception {
	TravelProxy travel = new TravelProxy();
	travel.setTravel(new TravelImpl());
	travel.vacation();
	// 去的车票购买
	// 度假...
	// 回的车票购买
}

1.2 动态代理

        JAVA动态代理是基于反射实现的,代理类的创建和方法都是在运行期完成的

1.2.1 基于接口的动态代理

        原理: 生成一个方法被增强接口的实现类的代理对象

        涉及的类: java.lang.reflect.Proxy

        涉及的接口: java.lang.reflect.InvocationHandler

        要求: 被代理的类至少实现一个接口

        示例代码:

@Test
public void travelTest() throws Exception {
	// 目标对象
	TravelImpl travelImpl = new TravelImpl();
	/**
	 * newProxyInstance参数说明:
	 *      ClassLoader(类加载器):将被代理类的字节码文件加载到JVM
	 *      Class[](字节码数组):被代理类实现的所有接口的字节码数组
	 *      InvocationHandler(拦截器,被代理类实现的任何"接口方法"都会被拦截)
	 *          proxy:代理对象的引用(一般不用)
	 *          method:被代理对象的方法对象(反射原理)
	 *          args:当前方法所需参数
	 */
	// 生成代理对象
	Travel travelProxy = (Travel) Proxy.newProxyInstance(TravelImpl.class.getClassLoader(), TravelImpl.class.getInterfaces(), new InvocationHandler() {
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			// 获取方法参数
			Object myName = args[0];
			// 获取方法名
			String methodName = method.getName();
			// 执行原始方法(有返回值)
				//参数1:目标对象
				//参数2,方法参数
			System.out.println("去的车票购买");
			Object methodReturn = method.invoke(travelImpl, args);
			System.out.println("回的车票购买");
			// 返回参数
			return methodReturn;
		}
	});
	// 执行代理对象
	travelProxy.vacation();
		//去的车票购买
		//度假...
		//回的车票购买
	travelProxy.study();
		//去的车票购买
		//去求学...
		//回的车票购买
}

1.2.2 基于子类的动态代理

        原理: 生成一个方法被增强该类的子类的代理对象

        引入jar包: cglib

        涉及的类: net.sf.cglib.proxy.Enhancer

        要求: 被代理的类不能是最终类(final,不能被继承)

        示例代码:

@Test
public void staticProxyTest() {
	// 目标对象
	TravelImpl travelImpl = new TravelImpl();
	// 代理对象
	TravelImpl travelProxy = (TravelImpl) Enhancer.create(TravelImpl.class, new MethodInterceptor() {
		@Override
		public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
			System.out.println("去的车票购买");
			Object methodReturn = method.invoke(travelImpl, objects);
			System.out.println("回的车票购买");
			return methodReturn;
		}
	});
	// 执行代理对象
	travelProxy.vacation();
}

2 AOP

        底层核心原理动态代理

        AOP(Aspect Oriented Programming)面向切面编程(使用动态代理技术,在不修改源码的基础上,给程序添加额外的功能)

2.1 注解开发

注解说明
@Aspect标识类为"切面类"
@Pointcut("execution(表达式)")

切入点表达式

例: public * xyz.aboluo.service.impl.*.*(..)

@Before前置通知
@AfterReturning后置通知
@AfterThrowing异常通知
@After最终通知(总会执行)
@Around环绕通知

        (1) 引入依赖

        (2) 切面类

@Component
@Aspect  //标识该类为一个"切面类"
@Order(1)  //针对"多切面",数字越小"越先执行,越后结束"
public class LogAdvice {

    @Pointcut("execution(public * xyz.aboluo.controller.*.*(..))")
    private void pc1() {
    }

    @Pointcut("execution(public * xyz.aboluo.service.impl.*.*(..))")
    private void pc2() {
    }

    @Pointcut("pc1() || pc2()")
    private void pc3() {
    }

    //前置通知
    @Before("pc2()")
    public void beforeAdvice() {
        System.out.println("前置通知,方法前执行");
    }

    //后置通知
    @AfterReturning("pc2()")
    public void afterReturningAdvice() {
        System.out.println("后置通知,方法后执行");
    }

    //异常通知
    @AfterThrowing("pc2()")
    public void afterThrowingAdvice() {
        System.out.println("异常通知,异常时执行");
    }

    //最终通知
    @After("pc2()")
    public void afterAdvice() {
        System.out.println("最终通知,始终执行");
    }

    //环绕通知
    @Around("pc2()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object methodReturn = null;
        try {
            System.out.println("前置通知");
            Object target = pjp.getTarget();  //获取被代理对象
            String methodName = pjp.getSignature().getName();  //获取当前方法名
            Object[] args = pjp.getArgs();  //获取当前方法执行所需的参数
            methodReturn = pjp.proceed(args);  //原方法执行
            System.out.println("后置通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知");
        } finally {
            System.out.println("最终通知");
        }
        return methodReturn;
    }
}

        (3) 切面执行顺序

                单切面: @Around --> @Before --> 原始方法 -->@After --> @AfterReturning --> @Throwing

                多切面: 使用@Order(1)注解,数字越"越先执行,越后结束"

3 声明式事务

3.1 事务(transaction)

        事务是数据库的,并不是java的,一个事务中的所有操作,要么全部执行,要么全部不执行

        事务的4大特性:

                (A) 原子性: 十五中的操作要么都生效,要么都不生效

                (C) 一致性: 事务执行前后,数据库中定义的约束,业务规则等保持不变

                (I)  隔离性: 并发访问数据库时,多个事物之间互不影响

                (D) 持久性: 如果事务被成功提交,数据会持久保存在数据库

        原子性、一致性、持久性都是单事务; 隔离性是多事务并发

        脏读: 读取到另一个事务未提交的数据

        不可重复读: 受其它事务update的干扰(期望是操作修改之前的数据)

        虚读/幻读: 读取不到其它事务的insert/delete,但操作数据时提示"数据异常"等问题

隔离级别脏读不可重复读虚读/幻读
read uncommitted
read committed (oracle默认)
repeatable read (mysql默认)
searializable

3.2 spring声明式事务

        底层原始是利用了AOP

        (1) 添加依赖

        (2) 配置事务管理器(注入数据源),开启spring对声明式事务注解支持

        (3) 在类/方法(方法优先级更高)上添加@Transactional注解

/**
 * readOnly=true
 *      只读:只能查询,不能增删改
 * timeout=10  //默认-1(永不超时)
 *      超时(秒):响应超时,回滚
 * 回滚策略:设置哪些异常回滚,哪些异常不回滚
 *      设置回滚:rollbackFor={Exception.class,RuntimeException.class}
 *              rollbackForClassName={"全限定类名"}
 *      设置不回滚:noRollbackFor={......}
 *                noRollbackForClassName={.....}
 * isolation  事务隔离级别:解决"读的问题"
 *      =Isolation.DEFAULT使用数据库的默认隔离级别
 *      =Isolation.READ_UNCOMMITTED
 *      =Isolation.READ_COMMITTED    (oracle默认,有"不可重复读")
 *      =Isolation.REPEATABLE_READ    (mysql默认,有"虚读/幻读")
 *      =Isolation.SERIALIZABLE
 * propagation  事务传播行为:事务之间的调用(例如:A调用B,那么要使用哪个的事务)
 *      Propagation.REQUIRED(默认):当前线程有事务就用当前事务,没有就新建事务
 *      Propagation.REQUIRES_NEW:不管当前线程有没有事务都新建一个事务,新事务与之前事务没有嵌套关系,之前的事务挂起
 */
@Transactional(readOnly = false, timeout = 10, rollbackFor = RuntimeException.class, isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED)
public void aaa() {
	userDao.aaa();
}

事务传播行为示例:

        有A,B两个事务,在A事务中循环调用B事务2次(第一次成功,第二次失败)

(1) A事务传播行为:REQUIRED,B事务传播行为:REQUIRED

@Transactional(propagation = Propagation.REQUIRED)
public void transactionA() {
	for (int i = 0; i < 2; i++) {
		transactionB();
	}
}

@Transactional(propagation = Propagation.REQUIRED)
public void transactionB() {
    // 第一次执行失败,第二次执行成功
}

        在一个线程中,B事务中执行的2次事务都回滚,因为B事务使用的是Propagation.REQUIRED事务传播特性,因为AB在同一个线程中,而且已经存在了A事务,所以B就沿用了A事务

(2) A事务传播行为:REQUIRED,B事务传播行为:REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRED)
public void transactionA() {
	for (int i = 0; i < 2; i++) {
		transactionB();
	}
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transactionB() {
	// 第一次执行失败,第二次执行成功
}

        在一个线程中,B事务中执行的第1次事务正常提交,第二次事务回滚,因为B事务使用的是Propagation.REQUIRES_NEW事务传播特性,因此每次transactionB方法执行都会新建一个新的事务(即B事务),新事务(B事务)与原事务(A事务)没有嵌套关系

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值