欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从一而终只使用一种Spring编程风格

程序员文章站 2022-03-02 12:08:00
...

代码

1、组件

现在很常见的就是不管如何先定义接口,如下所示:

 

package com.sishuok;

public interface Interface {
    public void sayHello();
}

 然后定义实现,真的有必要吗?思考下。

package com.sishuok;

public class Impl implements Interface {

    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}

Bean用于注入Impl,其实此处是错误的,因为定义了接口,应该注入接口的。

package com.sishuok;

public class Bean {

    private Impl impl;

    public Bean() {
    }

    public Bean(final Impl impl) {
        this.impl = impl;
    }
}

 

2、需要一个切面

怎么实现无所谓,就是为了生成代理对象,重现问题。

package com.sishuok;

public class Aspect {

    public void before() {
        System.out.println("==before");
    }
}

 

3、 配置文件

    <bean id="aspect" class="com.sishuok.Aspect"/>
    <aop:config>
        <aop:aspect ref="aspect">
            <aop:before method="before" pointcut="execution(* com.sishuok.Impl.*(..))"/>
        </aop:aspect>
    </aop:config>

    <bean id="impl" class="com.sishuok.Impl"/>

    <bean id="b" class="com.sishuok.Bean">
        <constructor-arg name="impl" ref="impl"/>
    </bean>

配置文件很简单。 一个AOP切面会横切Impl,即生成Impl代理,而Bean会注入Impl。

 

4、测试类

 

测试类很简单,只需要加载配置文件即可。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-config.xml"})
public class BeanIT {

    @Autowired
    private Bean bean;

    @Test
    public void test() {
        System.out.println("=hello test");
    }
}

 

整段代码很简单,应该能猜到是嘛问题。 

 

抛出的异常

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-config.xml]: Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)

 

这个异常让人疑惑,因为它告诉你说可能是构造器的问题,所以可能把我们带坑里看不到真实问题。

 

主要原因是:

我们实际定义了两个构造器:

1、一个空参的:public Bean() 

2、一个带Impl参数的:public Bean(final Impl impl)

 

如果把第一个构造器删除就会得到真实的异常:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in class path resource [spring-config.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.sishuok.Impl]: Could not convert constructor argument value of type [$Proxy8] to required type [com.sishuok.Impl]: Failed to convert value of type '$Proxy8 implementing com.sishuok.Interface,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised' to required type 'com.sishuok.Impl'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [$Proxy8 implementing com.sishuok.Interface,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [com.sishuok.Impl]: no matching editors or conversion strategy found

 

很明显是注入问题,见到$Proxy8 我们就能猜到是JDK动态代理(这个在《spring的二次代理原因及如何排查》说过),即它不能转换为实际的com.sishuok.Impl,问题很明显了.

 

此处还有另一个问题:关于按照构造器参数名注入时,具体参考《【第三章】 DI 之 3.1 DI的配置使用 ——跟我学spring3》中的构造器注入部分。

 

解决方案

1、要么构造器注入接口,即public Bean(final Interface impl)

2、要么使用CGLIB类代理

 

总结

1、知道自己使用的是啥编程风格:

  • 先定义接口,然后实现;如UserController---->UserService(接口 其实现是UserServiceImpl),这种情况大多数人都使用接口注入即可
  • 我喜欢直接定义实现,如UserController--->UserService(实现),因为这是一个模块内部的操作,干嘛定义接口呢?可扩展?想想自己扩展过吗?如果实在想要可扩展我一般是这样:UserApi UserApiImpl 可参考我的es脚手架

 

2、知道自己使用的啥代理:

  • JDK动态代理 还是 CGLIB代理, 做到心中有数,尽量别混用。当然最保险的方式就是使用CGLIB代理。

 

明确自己的风格,从一而终,不要为了玩玩都用上,Spring已经很庞大且复杂了,,使用Spring出问题最多的就是AOP部分,遇到AOP问题。可参考《请不要再使用低级别的AOP API

 

 

我喜欢

  • 给自己使用的无需定义接口;即一个模块内部的都是封装的,定义接口并不会得到很多好处,变过几次实现?? “优先面向接口编程,而非实现” 不是必须,是优先;
  • 给朋友(第三方)使用的定义接口;即要公开的功能,因为接口就是个契约,就是沟通用的;
  • 优先使用setter注入,除非必要才使用构造器注入;
  • 使用CGLIB代理,这样基本不会出现AOP代理注入不了或一些隐晦的问题;
  • 优先使用Spring提供的XML标签简化功能定义,如<aop:config>、<task:executor>等,而不要使用低层次API;
  • 尽量使用XML风格的事务,而不是注解风格;
  • 按照配置的内容分多配置文件存放配置,不要一股脑的放在一起,就像不分包那样;
  • 可配置部分(如db数据)还是放到XML中,不要什么都注解;
  • 使用Spring profile 或 maven profile分环境测试(如开发环境、测试环境、正式机环境);

官方文档还是要看的,当然刚开始可能比较困难,但是坚持几次以后就轻松了,出问题先看文档,接着翻javadoc,基本能搞定,当然读一读源码对日常开发的调错还是很有帮助的。