代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。
在不修改自身源码的情况下,达到增强功能的效果,这时候就可以使用代理来完成需求。
目标类 A 不能被 用户类 B 直接访问,只能通过中间商 C 来做桥接达到 A 访问 B 的效果。就像现实中的中间商一样,中间商一般不让客户直接联系供货商,如果客户直接联系供货商,中间商就没用了。而通过中间商来作为客户与供货商之间的桥梁,中间商就可以赚差价(增强功能),甚至阻断客户和供货商之间的联系(客户联系中间商,中间商不联系供货商,这样他们之间的联系就断了)。
静态代理就是在运行前,代理对象的内容就已经被确定了。
使用条件:代理类和目标类都需要实现统一的接口或者代理类直接继承目标类然后使用 super 来调用目标类方法来达到代理的效果。
public interface Fruit {
void sell();
}
public class Apple implements Fruit {
@Override
public void sell() {
System.out.println("卖苹果一斤");
}
}
public class AppleProxy implements Fruit{
private Apple apple;
public AppleProxy(Apple apple) {
this.apple = apple;
}
@Override
public void sell() {
System.out.println("偷吃一斤苹果");
this.apple.sell();
}
}
public class Run {
public static void main(String[] args) {
Fruit fruit = new AppleProxy(new Apple());
fruit.sell();
}
}
这里使用的是多态的思想,用户只关心 fruit.sell() 这个结果,不在乎是谁卖的(创建者)。
以上代码达到了 “卖苹果一斤” 的效果,中间商还加了一层buff “偷吃一斤苹果” ,等于是卖一次少两斤苹果。
不过缺点是如果代理商还想去代理其他水果,就必须新建一个代理类去代理。
动态代理解决了静态代理的缺点,也就是代理类内容必须是先确定的。
我们抛开其他工具类为我们提供的生成代理类的方法,自己想一下如果我们自己写一个动态代理需要什么东西。
首先分析一下静态代理,静态代理类实现了接口的所有方法,并且在需要代理的方法中增强了功能,每个方法增强的功能可以不一样,也可以一样。那么首先我们需要动态生成一个代理类,如果我们要为每一个目标方法提供不一样的增强功能,由于方法的不确定性,这个似乎有点难搞,那么我们就为所有需要代理的方法增加统一的增强功能。这时候我们除了生成一个代理类,我们还需要有一个定义增强功能的入口,这样生成代理类的时候为每个需要代理的方法写入增强功能。
接下来我们就来参考一下他们是怎么搞的。
常用的有 JDK 动态代理和 CGLIB 动态代理,我们先来看下 JDK 动态代理。
public interface Fruit {
void sell();
void purchase();
}
public class Apple implements Fruit {
@Override
public void sell() {
System.out.println("卖苹果一斤");
}
@Override
public void purchase() {
System.out.println("进货一斤苹果");
}
}
public class FruitInvocationHandle<T> implements InvocationHandler {
private T target;
public FruitInvocationHandle(T target) {
this.target = target;
}
/**
*
* @param proxy 生成的动态代理类
* @param method 被代理的方法
* @param args 被代理方法传递过来的参数
* @return 方法的返回值
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("偷吃一斤苹果");
return method.invoke(target, args);
}
}
public class Run {
public static void main(String[] args) {
Fruit fruit = (Fruit) Proxy.newProxyInstance(Fruit.class.getClassLoader(),
new Class[]{Fruit.class},
new FruitInvocationHandle<>(new Apple()));
fruit.purchase();
fruit.sell();
}
}
ok,我们跟着源码看看原理是啥。
main方法代码很少,主要的代码就一句 Proxy.newProxyInstance
。我们进 Proxy 看看 newProxyInstance 方法。
里面的验证、判断啥的我们不看,主要看这行代码 Class<?> cl = getProxyClass0(loader, intfs);
,这个是已经返回了代理类的Class类型了,我们再点进去看一下(Proxy的getProxyClass0):
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
没有啥实际的东西,再进去(WeakCache的get)->
主要看这行Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
的 subKeyFactory.apply(key, parameter)
,打个断点发现它走的是 Proxy 的 apply 方法,里面的这行就是生成字节码的实际动作了 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
,有兴趣的可以研究一下,这里就不细讲了。
那么我们知道 newProxyInstance 是返回一个代理类,因为动态代理类是运行在内存里面的,我们怎么知道它的具体内容呢?有两种方法:
第一种,使用 JDK 自带的 HSDB 调试工具。
找到 JDK 的安装目录,进入 lib 目录下,CMD 运行到当前目录,运行 java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
就会出现一个图形化的界面,这个就是 HSDB 了。
找到 Java 程序的进程 ID,先不运行程序,使用 jps
命令打印后台运行程序,然后再运行 Java 程序,多出的就是它的进程了。
依次点击 File-> Attach to Hotspot process。然后输入 Java 程序的进程ID,如果命令行界面出现报错Exception in thread "Thread-1" java.lang.UnsatisfiedLinkError: Can't load library: D:\jre\bin\sawindbg.dll
,则在 JDK 的安装目录搜索相关文件,复制到文件不存在的目录(D:\jre\bin\)。
如果是正常的,那么工具栏的 Tools 是可以点击的。依次点击 Tools -> Class Browser,输入代理类名称,点击右边的搜索按钮就可以了。那怎么找代理类的名称呢?在main方法上打个断点,让断点走到 fruit.purchase();
,Alt 按住点击 fruit ,第一行会出现 fruit = {$Proxy0@530} 其中 $Proxy0 就是它的类名称了,当然也可以到 Proxy 的 apply 方法,proxyName 中也有代理类名称。
搜索到类之后,点击这个它就会在 JDK 的安装目录下的 lib 目录根据包名创建相应的 class 文件了。
然后把 class 文件拖到 IDEA 中就可以查看内容了。
第二种就非常简单了,不过只针对于 Proxy 生成的动态代理类。
将 main 方法改成以下内容:
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
Fruit fruit = (Fruit) Proxy.newProxyInstance(Fruit.class.getClassLoader(),
new Class[]{Fruit.class},
new FruitInvocationHandle<>(new Apple()));
fruit.purchase();
fruit.sell();
}
运行之后在项目的根目录就能发现出现了个com目录,里面就有那个代理类了。
为什么说只针对于 Proxy 呢?看源码 sun.misc 包下的 ProxyGenerator 的 generateProxyClass 方法:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
当 sun.misc.ProxyGenerator.saveGeneratedFiles
为真时才写文件。
以下是代理类的内容:
public final class $Proxy0 extends Proxy implements Fruit {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) {
super(var1);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.rookie.stream.download.proxy.jdkproxy.Fruit").getMethod("purchase");
m4 = Class.forName("com.rookie.stream.download.proxy.jdkproxy.Fruit").getMethod("sell");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public final boolean equals(Object var1) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void purchase() {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sell() {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
可以发现,它每个方法都是调用的 invoke 方法,super.h -> 找到父类 Proxy 的 h,就是 InvocationHandler h 这个,也就是调用的我们传递的 FruitInvocationHandle。在 FruitInvocationHandle 的 invoke 中 method.invoke(target, args)
是用了反射去调用方法。
从生成的代理类来看,$Proxy0 必须继承 Proxy ,而又因为 Java 是单继承的,所以使用 JDK 动态代理,目标类必须实现接口,不然代理类引用不能替换目标类。
网上一些教程说的 JDK 是基于反射的,那么它说的这个基于反射是什么意思呢?它说的反射是在调用被代理方法时是使用反射去调用被代理方法的,也就是这句 method.invoke(target, args);
。
CGLIB 动态代理和 JDK 动态代理过程差不多相似,差在了 JDK 需要目标类实现接口,而 CGLIB 不用。
用法
public class Apple{
public void sell() {
System.out.println("卖苹果一斤");
}
public void purchase() {
System.out.println("进货一斤苹果");
}
}
public class AppleMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("偷吃一斤苹果");
return methodProxy.invokeSuper(o,objects);
}
}
public class Run {
public static void main(String[] args) {
// 在F盘根目录会生成代理类问题
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Apple.class);
enhancer.setCallback(new AppleMethodInterceptor());
Apple apple = (Apple) enhancer.create();
apple.sell();
}
}
Spring 中也维护了一份 cglib。
cglib 代理也是生成代理类,然后调用相关方法。我们看下生成的代理类,我们看主要的类文件内容:
public class Apple$$EnhancerByCGLIB$$312f8e2a extends Apple implements Factory {
……
public final void sell() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$sell$0$Method, CGLIB$emptyArgs, CGLIB$sell$0$Proxy);
} else {
super.sell();
}
}
……
}
可以发现也是每个方法都调用了我们配置的 MethodInterceptor 的 intercept 方法,在我们配置的 invoke 方法中为什么要调用 invokeSuper 方法呢?进入源码发现原来它是通过 switch 的方式根据传递过来的下标调用相关的方法。【当然也可以使用 method.invoke 去使用反射调用方法(注意 Object o 这个传递过来的是代理对象,需要通过构造方法或者其他方式将被代理对象传递进来,不然会陷入死循环,因为传递的是代理对象,调用的代理对象的invoke,invoke之后又调用的是代理对象的invoke,之后陷入死循环)但是这样就和 JDK 代理有点相似了。】
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
断点获取 fci.i2 的值,然后在包下会生成三个文件 Apple
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
312f8e2a var10000 = (312f8e2a)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
return new Boolean(var10000.equals(var3[0]));
case 1:
return var10000.toString();
case 2:
return new Integer(var10000.hashCode());
case 3:
return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
case 4:
return var10000.newInstance((Callback)var3[0]);
case 5:
return var10000.newInstance((Callback[])var3[0]);
case 6:
var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
return null;
case 7:
var10000.sell();
return null;
case 8:
var10000.setCallbacks((Callback[])var3[0]);
return null;
case 9:
312f8e2a.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
return null;
case 10:
312f8e2a.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
return null;
case 11:
return var10000.getCallback(((Number)var3[0]).intValue());
case 12:
return var10000.getCallbacks();
case 13:
var10000.purchase();
return null;
case 14:
return 312f8e2a.CGLIB$findMethodProxy((Signature)var3[0]);
case 15:
return new Integer(var10000.CGLIB$hashCode$5());
case 16:
return new Boolean(var10000.CGLIB$equals$3(var3[0]));
case 17:
return var10000.CGLIB$toString$4();
case 18:
return var10000.CGLIB$clone$6();
case 19:
var10000.CGLIB$purchase$1();
return null;
case 20:
var10000.CGLIB$sell$0();
return null;
case 21:
var10000.CGLIB$finalize$2();
return null;
case 22:
312f8e2a.CGLIB$STATICHOOK1();
return null;
case 23:
var10000.wait();
return null;
case 24:
var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
return null;
case 25:
var10000.wait(((Number)var3[0]).longValue());
return null;
case 26:
return var10000.getClass();
case 27:
var10000.notify();
return null;
case 28:
var10000.notifyAll();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
var10001 的值是 20 ,所以调用的就是 var10000.CGLIB$sell$0();
,在 Apple
final void CGLIB$sell$0() {
super.sell();
}
调用父类也就是 Apple 类的 sell 方法。从这里发现它和 JDK 代理的不一样了,直接调用与反射调用在效率上有些差别。所以说 JDK 代理是创建字节码文件快,执行慢,而 CGLIB 代理是创建字节码文件慢,执行快,不过经过 JDK的版本不断更新,反射的执行效率也逐渐的优化。有兴趣的可以研究一下他们之间的性能差异,我就不说了。
实现 AOP 常用的方式有两种,第一种是使用 Spring AOP ,第二种是使用 AspectJ。
Spring AOP 采用的是动态代理的方式来实现 AOP 的,使用 JDK 动态代理与 CGLIB 动态代理之间切换,基于 Spring,只能拦截 Spring IOC 容器中的 Bean。
AspectJ 是一种语言型的 AOP 框架,并且采用静态织入的方式,可以代理所有的 Java 类型。但是因为是通过修改源码的方式去实现 AOP ,所以需要使用独有的编译器去处理类。
我们知道 Spring AOP 使用的是动态代理的技术,Bean 在创建的时候返回的就是已经被代理的对象了,接下来我们来跟下源码查看一下具体是如何创建代理的。
测试代码:
@Aspect
@Component
public class AspectTest {
@Pointcut("execution(* com.rookie.stream.download.test..*.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void beforeRun(){
System.out.println("我是前置通知");
}
@AfterReturning("pointCut()")
public void doAfterReturning() throws Throwable {
System.out.println("我是无异常返回通知");
}
@After("pointCut()")
public void after(){
System.out.println("我是后置通知");
}
@AfterThrowing("pointCut()")
public void throwss(JoinPoint jp){
System.out.println("我是异常返回通知");
}
}
@Component
public class TestCase {
public void testPrint(){
System.out.println("我是测试内容");
}
}
# 执行结果
我是前置通知
我是测试内容
我是无异常返回通知
我是后置通知
其中 AfterThrowing 在方法执行异常时执行,AfterReturning 无异常时执行。
我们找到 AbstractAutoProxyCreator 的 postProcessAfterInitialization(后置处理器) 方法
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
spring 生命周期走到这里,通过 wrapIfNecessary 去尝试代理对象。
进到 AbstractAutoProxyCreator 的 wrapIfNecessary方法:
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
这行代码是获取所有匹配的通知,我们来看一下是如何获取的。
我们进到 AbstractAdvisorAutoProxyCreator 的 getAdvicesAndAdvisorsForBean 方法:
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
// 获取匹配的通知
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
我们再进到 AbstractAdvisorAutoProxyCreator 的 findEligibleAdvisors 方法:
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 拿到 IOC 容器中所有的 Advisor 类型的 Bean
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//获取匹配的通知
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
//给通知排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
进到 AbstractAdvisorAutoProxyCreator findCandidateAdvisors 方法 再到 BeanFactoryAdvisorRetrievalHelper 的 findAdvisorBeans 方法:
public List<Advisor> findAdvisorBeans() {
//获取缓存 Advisor Bean 的名称
String[] advisorNames = this.cachedAdvisorBeanNames;
//如果是第一次,缓存就是空的
if (advisorNames == null) {
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the auto-proxy creator apply to them!
// 获取 IOC 容器中的 Advisor.class 类型的 Bean,
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
if (advisorNames.length == 0) {
return new ArrayList<>();
}
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
// 这里总是返回true
if (isEligibleBean(name)) {
//过滤掉特殊的Bean
if (this.beanFactory.isCurrentlyInCreation(name)) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping currently created advisor '" + name + "'");
}
}
else {
try {
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping advisor '" + name +
"' with dependency on currently created bean: " + ex.getMessage());
}
// Ignore: indicates a reference back to the bean we're trying to advise.
// We want to find advisors other than the currently created bean itself.
continue;
}
}
throw ex;
}
}
}
}
return advisors;
}
回到 AbstractAdvisorAutoProxyCreator 的 findEligibleAdvisors 方法看一下 findAdvisorsThatCanApply 。
进到 AbstractAdvisorAutoProxyCreator 的 findAdvisorsThatCanApply 方法:
protected List<Advisor> findAdvisorsThatCanApply(
List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
// 通过AOP工具类去匹配通知
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
}
}
进到 AopUtils 的 findAdvisorsThatCanApply 方法:
主要是这些行代码
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
再走到当前类的 canApply 方法 public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions)
。再次走到 canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions)
,具体规则如下:
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
// TrueClassFilter 一直返回true
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set<Class<?>> classes = new LinkedHashSet<>();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
for (Class<?> clazz : classes) {
//获取类的所有方法
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
我们分别看下 introductionAwareMethodMatcher(debug的时候没有) 的 matches 与 methodMatcher 的 matches(这里我加的是Transactional通知,下面关于它的我就不写了)
methodMatcher
public boolean matches(Method method, Class<?> targetClass) {
if (TransactionalProxy.class.isAssignableFrom(targetClass) ||
PlatformTransactionManager.class.isAssignableFrom(targetClass) ||
PersistenceExceptionTranslator.class.isAssignableFrom(targetClass)) {
return false;
}
TransactionAttributeSource tas = getTransactionAttributeSource();
// 如果能解析出方法上的 Transactional 属性,那么 tas.getTransactionAttribute(method, targetClass) 就不是 null,所以这里只要加了 Transactional 注解就能返回 true
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
最终我们回到 AbstractAutoProxyCreator 的 wrapIfNecessary 方法,拿到了所有增强器,其中有一个默认类型的 ExposeInvocationInterceptor。走到这行开始正式创建代理对象了 Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
。
我们进到 AbstractAutoProxyCreator 的 createProxy 方法,重点在这行return proxyFactory.getProxy(getProxyClassLoader());
,我们进去看看:
进到 ProxyFactory 代理工厂的 getProxy 方法:
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
再进 ProxyCreatorSupport 的 createAopProxy 方法:
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 拿到代理工厂去创建 AOP 代理
return getAopProxyFactory().createAopProxy(this);
}
再进 DefaultAopProxyFactory 的 createAopProxy 方法:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!IN_NATIVE_IMAGE &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//如果 targetClass 是接口就用 JDK 动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则就用 CGLIB 动态代理
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
所以 Spring 在选用代理的时候会自动切换,一般优先看是否适合用 JDK 动态代理。
看完了如何选择代理方式,我们回到 ProxyFactory 的 getProxy 方法,看一下 getProxy的内容。
进到 CglibAopProxy 的 getProxy 方法
……
// Configure CGLIB Enhancer...
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
……
看到这些代码是不是很熟悉,没错,就是创建 CGLIB 动态代理的过程,好了,我们直接回到 AbstractAutoProxyCreator 的 wrapIfNecessary 方法,这时候的代理对象已经被创建好了,有了代理对象,我们看下 AOP 的这些通知是怎么被执行的。
我们找到 CglibAopProxy 的 DynamicAdvisedInterceptor 的 intercept 方法:
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
主要看这行代理 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
,返回的是被包装的通知。
我们进到 AdvisedSupport 的 getInterceptorsAndDynamicInterceptionAdvice 方法:
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
走到 cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass);
这行,再进入 DefaultAdvisorChainFactory 的 getInterceptorsAndDynamicInterceptionAdvice 方法,这个方法的作用就是把通知包装成 MethodInterceptor 返回,看到这行 Interceptor[] interceptors = registry.getInterceptors(advisor);
,里面有一些通知的适配器将通知包装成 Interceptor 类型。拿到拦截器链之后,我们直接返回 DynamicAdvisedInterceptor 的方法。
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
如果链是空的,就直接执行目标方法,如果不是,我们走到这里 retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
proceed 开始执行通知了。
进到 CglibAopProxy 的 CglibMethodInvocation 的 proceed 方法,再进 ReflectiveMethodInvocation 的 proceed 方法:
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
这行代码 this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1
是判断当前的下标是否是最后一个,有两种情况,第一种是没有通知,第二种是走到了最后一个通知。
protected Object invokeJoinpoint() throws Throwable {
if (this.methodProxy != null) {
return this.methodProxy.invoke(this.target, this.arguments);
}
else {
return super.invokeJoinpoint();
}
}
而最后一个代表着会执行目标方法。
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
会不断的自增去拿数组下的通知。
我们现在有 beforeRun、doAfterReturning、after、throwss这些通知。
代码走到这行 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
,第一次走我们拿到了默认的 ExposeInvocationInterceptor
到了 ExposeInvocationInterceptor 的 invoke 方法:
public Object invoke(MethodInvocation mi) throws Throwable {
MethodInvocation oldInvocation = invocation.get();
invocation.set(mi);
try {
return mi.proceed();
}
finally {
invocation.set(oldInvocation);
}
}
走到 return mi.proceed();
,然后又倒回了 CglibAopProxy 的 CglibMethodInvocation 的 invoke 方法。再一次的执行 ReflectiveMethodInvocation 的 proceed 方法,这次拿到了前置通知 beforeRun ,进入到MethodBeforeAdviceInterceptor 的 invoke:
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
走到 AspectJMethodBeforeAdvice 的 before 方法:
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
之……后走到 AbstractAspectJAdvice 的 invokeAdviceMethodWithGivenArgs 方法,这行 return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
使用反射method.invoke调用了前置通知。然后我们回到 MethodBeforeAdviceInterceptor 的 invoke,执行 return mi.proceed();
又回到了 CglibAopProxy 的 CglibMethodInvocation 的 proceed 方法,后续的通知都在往复的调用 proceed 方法。
第三次 AspectJAfterAdvice (后置通知)的 invoke
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
mi.proceed();
又回去(这个方法还没执行完,待会还会回来执行)。
第四次 AfterReturningAdviceInterceptor (无异常返回通知)的 invoke:
public Object invoke(MethodInvocation mi) throws Throwable {
Object retVal = mi.proceed();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
第五次的 AspectJAfterThrowingAdvice (异常返回通知)的 invoke:
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
if (shouldInvokeOnThrowing(ex)) {
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
这时候是最后一个通知了,所以执行了 ReflectiveMethodInvocation 的 proceed 方法的 invokeJoinpoint();
代码,也就是执行了目标方法,现在是打印了前置通知,目标方法。这时候因为几个 invoke 的循环调用,方法开始往回执行了,现在是 AspectJAfterThrowingAdvice,AfterReturningAdviceInterceptor(因为它没有被try包围,如果执行方法的时候出现异常,则会到 AspectJAfterThrowingAdvice 捕捉,然后执行它的异常返回通知)、AspectJAfterAdvice、MethodBeforeAdviceInterceptor、ExposeInvocationInterceptor。
这时候的通知已经全部打印完毕。
AspectJ
AspectJ 就不讲了,因为 Spring AOP 能解决大部分的问题,具体怎么使用可以看这个 https://blog.csdn.net/weixin_43697006/article/details/105265904
为什么Spring boot 不需要添加 EnableTransactionManagement 注解就能开启事务?我们找到 spring-boot-autoconfigure-2.4.1.jar 的 META-INF 文件下的 spring.factories 文件。发现 Spring boot 的自动装配已经把 org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
扫描进去了。点进类里面发现只要符合条件注解就会将事务注解添加上去,所以不用我们自己手动添加。
Spring 事务也是使用 AOP 技术去回滚的,我们可以来看下源码。
AOP 是寻找适配的增强器链,然后按照顺序执行增强器链。
事务代码:
@Transactional
public String saveData() {
int result = baseMapper.insert(new AccountEntity()
.setUserName("aj"+ LocalDateTime.now()));
int i = 1/0;
return "ok";
}
@Test
public void contextLoads() {
System.out.println(accountService);
accountService.saveData();
}
然后我们看一下生成的代理类:
public final String saveData() {
try {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$saveData$0$Method, CGLIB$emptyArgs, CGLIB$saveData$0$Proxy) : super.saveData();
} catch (Error | RuntimeException var1) {
throw var1;
} catch (Throwable var2) {
throw new UndeclaredThrowableException(var2);
}
}
还记得我们找 AOP 代理的开始吗?对,就是 AbstractAutoProxyCreator 的 postProcessAfterInitialization 方法:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
从这里开始给 Bean 创建代理。接下来就和上面的过程是一样的了,我们直接到增强器链那里。
我们找到 CglibAopProxy 的 DynamicAdvisedInterceptor 的 intercept 方法(直接进 CglibAopProxy 进不去,要先进 AopProxy 接口,然后再找实现类):
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
这里拿增强器链拿到了一条数据 TransactionInterceptor。然后去执行 ReflectiveMethodInvocation 的 process 方法,接着执行增强器链的 invoke 方法:
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
进入到 TransactionAspectSupport 的 invokeWithinTransaction 方法,这时候我们到了事务回滚的核心了。
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
// 解析事务注解属性
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//被拦截的方法名
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 这个方法会走 ReflectiveMethodInvocation 的 proceed 方法去调用目标方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
// 如果上面目标方法执行出现异常,这里会捕获异常去回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
}
catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
}
}
completeTransactionAfterThrowing 方法
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
rollbackOn 中的异常如果是 RuntimeException 和 Error 是可以正常回滚的,但是除了这两个异常还有 IOException 和 SQLException。
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
AbstractPlatformTransactionManager 的 processRollback 回滚逻辑
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
else {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
我这里走的是 status.isNewTransaction()
然后就是 DataSourceTransactionManager 的 doRollback 方法:
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
接下来我们针对性的说明几个问题。
在方法上未标注@Transactional
如果在需要事务回滚的方法上未标注@Transactional
注解,执行出现异常不会进行事务回滚。
假设有 A 方法需要在整个方法执行出现异常的时候进行事务回滚,那么在 A 方法上就必须加上 @Transactional
注解。
我们看一下如果在方法上不加 @Transactional
注解,方法会怎么走。
我们先找到 AbstractAutoProxyCreator 的 postProcessAfterInitialization 发现 AccountServiceImpl 类是被代理了的。
然后找到 CglibAopProxy 的 DynamicAdvisedInterceptor 的 intercept 方法,发现拿到的 chain 是空的,所以是会直接执行目标方法,而不会走 TransactionInterceptor 的增强器,也就不会进行回滚。
TransactionInterceptor 是在 ProxyTransactionManagementConfiguration 中加载的,加载步骤就是从 @EnableTransactionManagement
-> TransactionManagementConfigurationSelector -> ProxyTransactionManagementConfiguration。
回滚方法是private或者static或者final修饰
如果采用 CGLIB 动态代理的话,被代理的方法是需要能够被重写的,但 private 、static、final 都不能被重写。
回滚方法抛出的异常被拦截
如果我们在整个方法上加一个 try{}catch(Exception e){}
,并且异常不再抛出,那么这时候如果运行出现异常就不会回滚。回顾上面的源码分析知道,只有在方法执行异常,并且在增强器链上被捕获,那么才会进行事务回滚。
抛出的异常不是 RuntimeException 或者 Error 类型
这个从上面的源码我们可以知道,事务对于非 RuntimeException 或者 Error 类型是不做回滚需要我们自己处理的,并且 Spring 中对于 SQLException 进行了再次封装,所以有时候出现 SQLException 然后被 Spring 包装成了 RuntimeException 然后这个异常就可以被回滚了。
org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'hh' in 'field list'
### The error may exist in com/rookie/stream/download/mapper/AccountMapper.java (best guess)
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: update account set hh = 1 where id = 1
### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'hh' in 'field list'