Transaction在Controller层的探索

#Transaction在Controller层的探索

一般开发中事务要求我们放在Service层,可是有些情况,我们可能会要求放在Controller层,你有没有碰到过这样的需求呢?那么放到Controller层事务会生效吗?会产生什么问题呢?下面一起来看看

I、透过现象看本质

第一种情况

  • Controller层代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @RestController
    @RequestMapping("/city")
    public class CityControllerImpl implements CityController {
    @Autowired
    private CityService cityService;
    @Override
    @RequestMapping(value = "getCity",method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @Transcational
    public BaseResult<City> getCity(@RequestParam("id") Integer id) {
    City one = cityService.getOne(id);
    BaseResult<City> baseResult=new BaseResult<>();
    baseResult.setData(one);
    return baseResult;
    }
    }
  • 运行结果

    Transaction ERROR controller

  • 对的,你没有看错,当Transactional加载Controller层时出现404异常

第二种情况

  • Controller层代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @RestController
    @RequestMapping("/city")
    public class CityControllerImpl {
    @Autowired
    private CityService cityService;
    @RequestMapping(value = "getCity",method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @Transactional
    public BaseResult<City> getCity(@RequestParam("id") Integer id) {
    City one = cityService.getOne(id);
    BaseResult<City> baseResult=new BaseResult<>();
    baseResult.setData(one);
    return baseResult;
    }
    }
  • 跟上面的区别,就是没有实现CityController接口了,那么我们运行一下,会有什么结果呢?

  • 运行结果如下:

    1
    2
    3
    4
    5
    {
    data: null,
    message: null,
    status: 0
    }
  • 第二种情况居然没有啥问题,那么Transactional是否正常回滚呢?这里答案我直接告诉大家了,即使是换成有数据更改的接口,我们的事务是生效的。

  • 下面我为大家看源码解释一下

第三种情况

  • 笔者测试使用支持==JAX-RS 2.0==的 Resteasy 测试,发现是没有这个问题的,大家可以自测一下Jersey是不是存在这个问题,推断应该没有

II、熟悉本质解现象

1. 区别

可以看出,我们两个Controller的区别就是一个有实现接口,一个没有实现,为什么差别会这么大呢?

2. 事务的本质

我们知道事务是基于代理实现的,目前Spring中有JDK动态代理和CGLIB代理两种代理,那么跟Spring选择的代理有没有关系呢?我们看一下Spring在代理类的时候选择使用何种代理的源代码。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (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.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}

这是Spring创建代理比较核心的一段代码,在类 DefaultAopProxyFactory 中,不管加没有加接口,Spring看到了@Transactional注解都会给我们的Controller注册为一个代理对象。注意:Spring并非对所有的Controller都会创建代理类,假如我们的Controller没有暴露任何切面,Spring并不会创建一个代理类,这里可能大家会感到奇怪,我们这里打个TAG,文末讲解。

继续刚刚的话题,第一种情况,由于我们的Controller有接口,所以就走了JDK代理,相反第二种走了Cglib代理。OK, 我们的CityControllerImpl现在是一个代理类。那么为什么会发生404异常呢?

3. SpringMvc的原理

为什么Controller变成代理之后,就会404异常了,肯定跟我们的SpringMVC有关,我们看一下SpringMVC的核心类 AbstractHandlerMethodMapping 这个类可以绑定URL和需要执行处理器的哪个方法。这个抽象类实现了initializingBean接口,其实主要的注册URL操作则是通过这个接口的afterPropertiesSet()接口方法来调用的。然后调用initHandlerMethods 方法进行绑定URL。方法详细如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}

beanType中取出来是 CityControllerImpl 代理类,这里大家注意,代码第21行,有一个isHandler方法,这个方法用于判定这个类是不是Handler,其中代码如下:

1
2
3
4
5
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

看到这里相信大家已经很明白了,这里就是看你这个类上面有没有Controller注解和RequestMapping注解。如果有,就建立相关的映射关系(URL->Handler)

  • 其中有接口的是被JDK代理的,生成的是JDK代理类

    JDK的动态代理是靠多态和反射来实现的,它生成的代理类需要实现你传入的接口,并通过反射来得到接口的方法对象,并将此方法对象传参给增强类的invoke方法去执行,从而实现了代理功能。

    CityController生成的代理类文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public final class cityControllerImpl extends Proxy implements Proxy86 {
    private static Method m1;
    private static Method m32;
    private static Method m7;
    public cityControllerImpl(InvocationHandler var1) throws {
    super(var1);
    }
    public final TargetSource getTargetSource() throws {
    try {
    return (TargetSource)super.h.invoke(this, m8, (Object[])null);
    } catch (RuntimeException | Error var2) {
    throw var2;
    } catch (Throwable var3) {
    throw new UndeclaredThrowableException(var3);
    }
    }
    public final void addAdvice(int var1, Advice var2) throws AopConfigException {
    try {
    super.h.invoke(this, m21, new Object[]{var1, var2});
    } catch (RuntimeException | Error var4) {
    throw var4;
    } catch (Throwable var5) {
    throw new UndeclaredThrowableException(var5);
    }
    }
    public final BaseResult getCity(Integer var1) throws {
    try {
    return (BaseResult)super.h.invoke(this, m27, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
    throw var3;
    } catch (Throwable var4) {
    throw new UndeclaredThrowableException(var4);
    }
    }
    }

    类已经被精简过,我们看到生成的代理类中完全没有@Controller @RequestMapping 注解,所以isHandler方法执行失败,所以根本不会加到SpringMvc的控制器处理方法中去,当URL请求过来的时候,找不到对应的处理器处理,所以就报404错误啦

  • 没有接口的是被CGLIB代理的,生成的是CGlib代理类

    CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class CityControllerImpl$$EnhancerBySpringCGLIB$$8cae5808 extends CityControllerImpl implements SpringProxy, Advised, Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    final BaseResult CGLIB$getCity$0(Integer var1) {
    return super.getCity(var1);
    }
    public final BaseResult getCity(Integer var1) {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (this.CGLIB$CALLBACK_0 == null) {
    CGLIB$BIND_CALLBACKS(this);
    var10000 = this.CGLIB$CALLBACK_0;
    }
    return var10000 != null ? (BaseResult)var10000.intercept(this, CGLIB$getCity$0$Method, new Object[]{var1}, CGLIB$getCity$0$Proxy) : super.getCity(var1);
    }
    }

    ==其实isHandler方法会代理类的接口和父类进行扫描==,看你有没有这个注解,JDK代理中cityControllerImpl接口和父类都没有注解,而CGlib代理的父类是CityControllerImpl 这个原始的类, 所以返回为真

III、思考

如果Controller层不加@Transcational注解的时候,为什么又不会产生404异常呢?其实如果你Controller不加任何织入代码的话(自定义aop切面等,有兴趣的可以用AspectJ试一下织入Controller层的Method方法会发生什么事情),Spring是不会给你的类生成代理的,也就是在AbstractHandlerMethodMapping 绑定的时候,这个类不是一个代理,所以才会匹配成功。

Share

datatables服务端分页配置

datatables服务端分页配置

datatables可以说是个好东西吧,几行js代码就能够让你初始化一个表格,很方便,但是要用好还是很困难的,因为配置太多太多了,网上的大多数是一些陈旧的配置,如果datatables更换了这么多版本,网上很多配置都有一些问题,我这里也不是datatables的基础教程,我这里就谈一下服务端分页怎么配置,因为我项目中用到了,笔以记之。后之览者,亦将有感于斯文~

一、必备

  1. 引入

    1
    2
    3
    4
    5
    6
    <!--引入css-->
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css">
    <!--引入JavaScript-->
    <script type="text/javascript" language="javascript" src="//code.jquery.com/jquery-1.12.4.js"></script>
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.15/js/jquery.dataTables.min.js"></script>

Read More

Share

利用HttpServletResponseWrapper包装reponse

利用HttpServletResponseWrapper包装reponse

题记: 很多人或许用Spring的时候统一修改一下reponse信息,比较常见的就是给所有的response加一个status、code、message之类的要求,也有的想把response和request的日志打印出来,那么该如何做呢?那就耐心看看吧

一、背景介绍

  对于上述我们说到的这个需求,有很多中实现方式,比较容易想到的就是拦截器,过滤器等,这里我使用的是过滤器的方式来实现的,一是因为Spring天然就对Filter支持得比较好,二是开发方便容易。

  对于过滤器的实现方式,我这里也提供2种方式来实现,一种是复制ServletOutputStream的方式,一种是不复制ServletOutputStream的方式,两种方式各有用处,随君选择,后续我会开一篇原理的文章

Read More

Share

Spring filter原理深入浅出

Spring filter原理深入浅出

在这篇文章中,我会介绍一个请求从浏览器进来,是如何被Spring拦截到的,Spring又是如何处理这个请求的,filter有什么作用,filter的原理等

一、前置知识

OK,我们首先介绍一下,请求从浏览器过来,我们的服务器会经历些什么。先看一张jetty的内部结构图

Read More

Share

Spring Boot AOP系列之AOP原理介绍

Spring Boot AOP系列之AOP原理介绍


序言:在本文中,jdk动态代理和cglib代理的核心原理,我都会一一讲到,看本文的时候可以结合着上文看,了解创建的过程,创建的生命周期,才能知晓原理

一、JDK动态代理原理

1、前言

其实大家看了上文的demo肯定会有所疑惑,为什么实现了一个InvocationHandler接口就可以实现切面的植入了,就可以在写好的函数方法上面,植入我们的代码呢?是不是很神奇,惊不惊喜,意不意外。OK,废话不多说,下面跟着我的思路,走一遍源代码。

2、介绍

上文中提到了,Spring jdk动态代理最核心的一句话莫过于下面这句话

1
2
3
4
HelloWorld helloWorld=(HelloWorld)Proxy.
newProxyInstance(JDKProxyTest.class.getClassLoader(),
new Class<?>[]{HelloWorld.class},
new MyInvocationHandler(new HelloworldImpl()));

走进newProxyInstance这个方法

1
2
3
4
5
6
7
8
9
10
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 从缓存中取出或者生成代理
*/
Class<?> cl = getProxyClass0(loader, intfs);

Read More

Share

Spring Boot AOP系列之动态代理创建

Spring Boot AOP系列之动态代理创建


序言:在本文中,我会讲到Spring中如何创建动态代理,如何利用jdk或者cglib来创建动态代理,但是同时,我也会提及如何手动创建代理,这将有助于我们了解动态代理的原理。我们先来讲讲如何手动创建吧。

一、 手动创建之jdk动态代理

1、 jdk动态代理是基于接口的,所以必须要写一个接口,以及实现类,然后还需要写一个类来实现InvocationHandler类,

接口如下

1
2
3
public interface Helloworld {
void sayHello();
}

实现类如下:

1
2
3
4
5
public class HelloworldImpl implements HelloWorld {
public void sayHello() {
System.out.print("hello world");
}
}

Read More

Share

Fastjson解析多级泛型

Fastjson解析多级泛型

前言

现在网上大多数是使用TypeReference 方式来解析JSON数据,这里我提供另外一种方式来解析,使用类文件进行解析,两种方式我都会给出实际代码

实例

TypeReference方式
1
2
3
4
5
6
@GET
@Path("getUserCourse")
@Produces(MediaType.APPLICATION_JSON)
public Result<List<UserCourseDto>> getUserCourse(){
return externalBiz.getUserCourse();
}
1
2
3
4
public Result<List<UserCourseDto>> getUserCourse(){
String result = restTemplate.getForObject(MOCK_JSON_URL, String.class);
return JSONObject.parseObject(result, new TypeReference<Result<List<UserCourseDto>>>() {});
}

Read More

Share

Spring Boot 集成 resteasy篇 — jax-rs初步介绍和spring boot集成

Spring Boot 集成 resteasy篇 — jax-rs初步介绍和spring boot集成

JAX-RS 是代表restful web service的一套规范API,JAX-RS规范基于JAVA编程语言,它是用来创建Restful 风格的web services服务的。Jax-rs使用一系列注解来简化java开发。

JAX-RS也称jsr339 (全称java specifications requests java规范提案第339个),由jcp(java community process)组织经过投票通过。

为了加深对jsr的理解,举一个简单的例子,jsr 330标准就是依赖注入的提案,已经通过。

规范说明

规范定义了一些常见的注解(见下表),用来标注一种资源文件为restfun风格的资源文件

序号 注解 描述
1 @Path 类或者方法的相对地址,标注在类或者方法上
2 @GET HTTP GET请求用,用来获取资源
3 @PUT HTTP PUT 请求,用来创建资源
4 @POST HTTP POST 请求, 用来创建或者更新资源
5 @DELETE HTTP DELETE请求, 用来删除资源
6 @HEAD HTTP HEAD请求, 用来获取一个接口是否可用的状态
7 @Produces 返回数据的格式比如APPLICATION/XML, TEXT/HTML, APPLICATION/JSON
8 @Consumes 请求数据格式 , 如json
9 @PathParam 绑定url里面的参数
10 @QueryParam 绑定url后面的参数(?后面的)
11 @MatrixParam 绑定包含多个 property (属性)=value(值) 方法参数表达式
12 @HeaderParam 给header里面传参
13 @CookieParam 给cookie里面传参
14 @FormParam 给form传参
15 @DefaultValue 给参数一个默认值
16 @Context 可以注入HttpRequest,HttpResponse用

规范实现

下文将以resteasy为例

Read More

Share

Spring Boot 集成 resteasy篇 — jax-rs常用注解介绍

Spring Boot 集成 resteasy篇 — jax-rs常用注解介绍

@GET

http请求方式里面的get请求,标记这个请求方式为get

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* http://localhost:8080/v1/province/city/1
* @param id
* @return
*/
@Path("city/{id}")
@GET
@Produces(MediaType.APPLICATION_JSON) //表示输出json
@Consumes(MediaType.APPLICATION_JSON) //表示输入为json
public Result<Boolean> getCity(@PathParam("id") Long id) {
return new Result<>();
}

Read More

Share

高可用数据同步方案-SqlServer迁移Mysql实战

高可用数据同步方案-SqlServer迁移Mysql实战

简介

随着业务量的上升,以前的架构已经不满足业务的发展,数据作为业务中最重要的一环,需要有更好的架构作为支撑。目前我司有sql server转mysql的需求,所以结合当前业务,我挑选了阿里云开源的一个很好用的同步工具DataX

DataX介绍

DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、MaxCompute(原ODPS)、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。

框架设计

datax_framework_new

DataX本身作为离线数据同步框架,采用Framework + plugin架构构建。将数据源读取和写入抽象成为Reader/Writer插件,纳入到整个同步框架中。

  • Reader:Reader为数据采集模块,负责采集数据源的数据,将数据发送给Framework。
  • Writer: Writer为数据写入模块,负责不断向Framework取数据,并将数据写入到目的端。
  • Framework:Framework用于连接reader和writer,作为两者的数据传输通道,并处理缓冲,流控,并发,数据转换等核心技术问题。

Read More

Share