1. 账户的转账操作中的数据库连接:
下图操作中有四个连接,而合理的情况为只有一个连接。
需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程只有一个能控制事物的(连接)对象。
2. 事务控制实现一致性
3. 动态代理
特点:字节码随用随创建,随用随加载。和装饰者模式不同,装饰者模式必须提前写好一个类。
作用:不修改源码的基础上对方法增强
分类:基于接口的动态代理和基于子类的动态代理
3.1 基于接口
Proxy类,由JDK官方提供
3.1.1 创建代理对象:被代理类最少实现一个接口,如果没有则不能使用。
Proxy类中的newProxyInstance方法
返回Object类型,需要强转
参数如下:
ClassLoader loader:类加载器
用于加载代理对象字节码,写的是被代理对象的类加载器,属于固定写法
比如Object.getClass().getClassLoader()
Class<?>[] interfaces:字节码数组
用于让代理对象和被代理对象有相同方法(调用其接口的字节码),属于固定写法
Object.getClass().getInterfaces()
InvocationHandler h:提供增强的代码
写代理方法,写一个该接口的实现类,通常为匿名内部类,但不是必须的
例:
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何方法都会经过该方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float) args[0];
//2.判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
//这里传入的对象必须是final修饰的
}
return returnValue;
}
});
3.2 基于子类
涉及Enhancer类,由第三方库cglib提供
创建代理对象使用Ehancer类中的create方法
被代理类不能是最终类
Enhance.create(Class class, Callback callback)
参数:
Class class: 字节码,指定被代理对象的字节码
Callback callback:用于提供增强的代码
一般写的都是该接口的子接口实现类:MethodInterceptor
AOP | Aspect Oriented Programming | 面向切片编程
1. 作用:不修改源码对已有方法进行增强
2. 实现方式:使用动态代理
3. Spring中的代理对象通过配置实现
4. xml配置
4.1 spring中基于XML的AOP配置步骤
4.1.1 把增强(advice)bean交给Spring管理
4.1.2 使用aop:config标签表明开始AOP的配置
4.1.3 使用aop:aspect标签表明配置切面
id属性:给切面提供一个唯一标识
ref属性:指定advice类bean的Id
4.1.4 在aop:aspect标签的内部使用对应标签来配置增强的类型
案例是使用前置增强
aop:before 表示配置前置增强
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:指定切入点表达式,表达式含义指对业务层中哪些方法增强
<!--配置spring的ioc,把service对象配置进去-->
<bean id="accountService" class="com.alan.service.impl.AccountServiceImpl"></bean>
<!--spring中基于xml的AOP配置步骤-->
<!--配置logger类-->
<bean id="logger" class="com.alan.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
4.2 切入点表达式:
4.2.1 关键字execution
execution(访问修饰符 返回值 包名.包名.类名.方法名(参数列表))
4.2.2 全通配写法: * *..*.*(..)
访问修饰符可以省略
返回值可以使用通配符*,表示任意返回值
包名可以使用通配符,表示任意包,有几级包写几个*.
包名可以使用*..来表示包及其子包
类名和方法名都可以使用*来实现通配
参数列表:
直接写数据类型:
基本类型直接写名称 如int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符*表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有写法
* com.alan.service.impl.*.*(..)
4.3 四种增强类型:
<bean id="accountService" class="com.alan.service.impl.AccountServiceImpl"></bean>
<!--spring中基于xml的AOP配置步骤-->
<!--配置logger类-->
<bean id="logger" class="com.alan.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,建立通知方法和切入点方法的关联-->
<!--切入点方法执行之前-->
<aop:before method="beforePrintLog" pointcut="execution(* *..*.*(..))"></aop:before>
<!--切入点方法正常执行之后-->
<aop:after-returning method="afterPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-returning>
<!--切入点方法异常执行之后-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-throwing>
<!--切入点方法结束后(无论是否正常执行)-->
<aop:after method="finalPrintLog" pointcut="execution(* *..*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
切入点表达式标签
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,建立通知方法和切入点方法的关联-->
<!--切入点方法执行之前-->
<aop:before method="beforePrintLog" pointcut-ref="dt1"></aop:before>
<!--切入点方法正常执行之后-->
<aop:after-returning method="afterPrintLog" pointcut-ref="dt1"></aop:after-returning>
<!--切入点方法异常执行之后-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="dt1"></aop:after-throwing>
<!--切入点方法结束后(无论是否正常执行)-->
<aop:after method="finalPrintLog" pointcut-ref="dt1"></aop:after>
<!--配置切入点表达式 id属性用于指定表达式的唯一标志, expression指定表达式内容
写在aspect内只能此切面有效
写在aspect外所有切面都有效
-->
<aop:pointcut id="dt1" expression="execution(* *..*.*(..))"></aop:pointcut>
</aop:aspect>
</aop:config>
4.4 环绕增强
spring中的环绕通知是spring框架提供的一种可以在代码中手动控制增强方法合适执行的方式
配置环绕增强后,切入点方法没有执行而增强方法执行。
分析:在动态代理中环绕增强有明确的切入点方法调用位置而此时代码里没有
解决:Spring提供的ProceedingJoinPoint接口作为环绕增强的方法参数,有一个proceed()方法,此方法明确调用切入点方法。
注:
基于XML的AOP及IOC
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<!--ref引用切面类-->
<aop:aspect id="beanAdvice" ref="beanFactory">
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
<aop:pointcut id="pt1" expression="execution(*
com.alan.service.impl.AccountServiceImpl.*(..))"></aop:pointcut>
</aop:aspect>
</aop:config>
</beans>
基于注解的AOP及IOC
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.alan"></context:component-scan>
<!--配置spring开启注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>