Spring 学习

  • 异常收集
  • IOC:
    • 常用实现类:
    • Spring对bean的管理细节:
    • Spring依赖注入
    • 基于注解的IOC配置
  • AOP
    • Maven依赖配置文件.xml
    • Spring配置文件.xml
    • AOP标签:(基于xml的AOP配置)
    • 基于注释的AOP配置
  • Spring中的JdbcTemplate
  • Spring提供的事务控制
    • 基于xml+注解的实现
  • java动态代理知识

概念:Spring是分层的JavaSE\EE轻量级开发框架。以IOC(Inverse Of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)为内核。

名词说明

  • bean:可重用组件。通常指被依赖、被反复调用的方法或类。也是Spring实现IOC的理念:通过管理bean的自动创建而进行类(方法)间解耦。

耦合和解耦:

耦合:类和方法之间的依赖。

解耦:降低程序间的依赖关系。

实际开发中:为保证类的独立性,应做到编译期不依赖,运行时才依赖。大概思路如下:

  • 使用反射而非new创建对象:

    如在java中连接mysql,首步是注册驱动。

    此时应该用Class.forName("com.mysql.cj.jdbc.Driver")(依赖于字符串)

    而非DriverManager.regesiterDriver(new com.mysql.cj.jdbc.Driver())(依赖于一个类);

  • 通过读取配置文件来获取类的全限定名:

    上述方法中,使用了固定字符串,其结果是想要使用别的数据库时,需要修改而非拓展。其耦合性还是蛮强,如果使用配置文件获取全限定名的方式,即可更好地解决。

  • 如果可以的话,使用单例模式。


核心容器内容(Core Container)

IOC:

作用:降低类之间的依赖关系、耦合强度。

通用定义:将创建对象的权利交给框架(自己也可写代码可以用工厂模式实现),降低本类或方法,对依赖类的耦合性。

Spring中的IOC:

用ApplicationContext接口的实现类来实现。其功能相当于我们自己编写了工厂类、读取配置文件加上控制反转。形成了解耦效果。不过其有更多的拓展功能,更灵活。

前言:对于抽象类、接口等,可以在eclipse中,看其实现类或父类。F4

常用实现类:

  • ClassPathXmlApplicationContext:可以加载类路径下的xml配置文件
  • FileSystemXmlApplicationContext:可以加载任意磁盘位置下的xml配置文件(有访问权限)
  • AnnotationConfigApplicationContext

部分接口的运作区分:

加载类的时机:

  • ApplicationContext:采用立即加载策略。当读到配置文件时,立刻加载配置文件中所有的类。(单例对象适用,但该接口很智能的,会根据类的属性是单例和多例进行加载策略切换,实际开发中多用该接口的实现类)
  • BeanFactory:采用延迟加载策略。只有到使用时,才加载类(初始化创建)。(多例对象适用)

Bean

在配置文件.xml中:

格式:

标签

  • :主要的标签,其<>内有多个属性。
  • :位于标签内部,用作构造函数中的参数注入。详细看下方依赖注入
  • :位于标签内部,用作set方法的参数注入。
    • 标签有很多的子标签:(结构相同,标签可以互换)
    • List结构集合注入的标签:
      • 通用子标签:value
    • Map结构集合注入的标签:
      • 子标签:

属性:

  • id:每个bean以id来标识,其代表的是一个可重用的组件(类);默认情况下创建的类是单例的。

  • class:加载该类的路径。

  • scope:作用范围,一般用于切换单例或者多例模式。

  • init-method:用于指定该bean初始化的构造函数。

  • destroy-method:用于指定该bean的析构函数。

  • factory-bean:用于引用已在核心容器IOC(配置文件.xml)里面创建的bean。其值为id。

  • factory-method:与前者配合,用于返回一个方法的结果(类),其值为functionName。

  • 对于标签的属性:

  • name:通过形参名字匹配形参。(最常用)

  • index:通过索引匹配形参。(仅constrctor-arg)

  • type:通过类型匹配形参。(仅constructor-arg)

  • value:给基本类型形参赋值,与上面三个相对应。

  • ref:给类型数据形参赋值,其通过引用已有的,给形参传入非基础类型的类。

Spring对bean的管理细节:

  • 创建bean(需要的类)的三种方式:

    • 使用默认构造函数创建:

      配置文件:

      spring只能通过默认构造函数来创建对象。如果没有默认构造函数会报错(当创建了带参数的构造函数时,且没有创造不带参的{默认}构造函数时)。

    • 使用一个工厂类的方法返回值创建:(多用于jar包中.class文件,无法修改)

      配置文件:

      spring会先创建工厂类,并将其作为可重用组件,然后调用其public方法,得到一个返回的建好的目标类。

      注意:经实验工厂的方法可以是private,仍然可以正常得到一个返回值(类)。

    • 使用一个工厂类的static方法返回值创建:(也是多用于无法修改的jar包)

      配置文件:;如上所述。

  • bean对象的作用范围:

    说明:用中的属性scope来定义作用范围,其有以下取值:

    • singleton:单例模式(默认)
    • prototype(原型):多例模式。其作用如:每次调用ClassPathXmlApplicationContext(“bean.xml”)创建一个新的对象。(此时跟BeanFactory一样,延迟加载)
    • request:作用于web应用的请求范围。
    • session:作用于web应用的会话范围。
    • global-session:作用于集群环境的会话范围。当非集群时,其效果等同session。
  • bean对象的生命周期:

    说明:属性scope决定了不同的生命周期。scope:

    生命周期:出生、存活、死亡。

    • singleton:生命周期与spring框架一样。
    • prototype:出生:使用时创建。死亡:当离开其作用域时GC回收销毁。

Spring依赖注入

说明:IOC的目的是降低类间耦合,使用Spring框架来管理依赖,我们只需要在配置文件中说明依赖关系的维护,称为依赖注入。

可注入的类型:三类:

  • 基本数据类型包括String
  • bean:引用配置文件中已设置的
  • 复杂类型\集合类型

注入的方式:三种:

  • 使用构造函数提供:

    如:

    
    
      
      
      
    
    
  • 优点:如果类被创建,则参数一定以及被设置。不会忘掉提供参数注入,若没有则无法创建。

  • 缺点:当我们在不需要这些使用这些参数时,仍然需要提供。

  • 使用set方法提供(与构建函数优缺点相反)(更常用灵活)

    先在类中对成员变量,提供相应的set方法。可以在eclipse或idea中用generate set生成。

    如:

    private String name;
    private Set mySet;
    private Map myMap; 
    
    public void setName(String name) {
      this.name = name;
    }
    public void setMySet(Set mySet) {
      this.mySet = mySet;
    }
    public void setMyMap(Map myMap) {
      this.myMap = myMap;
    }
    

    然后设置配置文件.xml。

    如:

    
    
          
          
          
    
    
    • 优点:当不需要参数时,可以不提供,或者提供部分参数。
    • 缺点:如果某成员必须有值,但获取对象时,set方法可能并没有执行。

    set方法实现复杂\集合数据的注入:

    说明:方法与上类似,就是在配置文件.xml中,在该里面会存在子标签,分为两类:List标签,和Map标签。标签名字在前文有标注。

    注意,下面代码中Map和Prop都是Map结构,其标签可以互换使用。List结构的也一样可以互换。

    如:

    
        "111"
        "222"
        "333"
    
    
          
              this is prop1
              this is prop2
              
    
    
        
           
               
                
    
    

    详情可以看ubuntu eclipes SpringLearn项目。

  • 使用注解提供:看后文。


基于注解的IOC配置

说明:注解所提供的功能效果与上述配置xml文件一样。只是实现方式的不同。

以下根据不同作用介绍不同格式:

创建对象:(放入组件)

注解:四个注解功能几乎完全一样,可以互换,最主要是为了三层对象更清晰。

  • @Component:一般用于表现该类不属于三层体系。
  • @Controller:一般用在表明该类是表现层UI
  • @Service:一般表明该类是业务层Service
  • @Repository:一般表明该类是数据层DAO
  • 配置类的注释中请看下文:配置类

例子:

类中:在java类中,于类体上一行,加上注解@Component(""),如:

@Component("NewService4")
public class NewService4 {
    public void run() {
        System.out.println("NewService is running...");
    }
}

bean.xml中:

①先配置主标签的属性如:可在官网文档core中查到;

②导包:导入NewService4所在的包,可以是祖先目录也可以是当前目录。也可以用下方的配置类来代替

标签格式



   
    
    
    ...

配置configuration类(*)

①在java文件夹下,新建config包,并在其中新建一个或多个configuration类。

@Configuration:注解该类,作用:指明该类是一个配置类。

@ComponentScan:指明组件扫描路径。@ComponentScan("com.path")

@Bean:在类里使用,将方法的返回值(类),放到容器中,其在容器中的key默认为方法名,可以使用属性指定id名。@Bean(name="IdOfContainner")

当该类有参数时,Spring框架会去容器中查找是否有符合的Bean,方式与注释类型注入一样。

容器类型

AnnotationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

其他:

  • 当要修改bean组件的Scope时,使用@Scope(...)
  • 当有多个配置类时,可以在一个配置类中导入其他配置类:@Import(OtherConfig.class)。当使用时,本类是主配置类,而@Import中导入的类都是子配置类,可以导入多个类,因为@Import方法中存放的是字节码数组。(父子关系的配置更合理和清晰)
  • 使用properties配置如数据库连接之类的信息(properties文件主要用于将key映射为全限定名)
    • @PropertySource:注释用于指定 .property文件的路径。其值为:@propertySource("classpath:...")classpath是限定符。

具体看Ubuntu--Eclipse--XmlIOC项目

例子:

@Configuration
@ComponentScan("com.yinghuo.XmlIOC")
public class SpringConfiguration {
    
    @Bean(name="runner")
    public QueryRunner createQueryRunner(DataSource ds) {
        return new QueryRunner(ds);
    }
    
    @Bean(name="DataSource")
    @Scope(prototype)
    public DataSource createDataSource() {
        ...
    }
}

其他注解

  • @Before:可以相当于初始化init-method使用,用于注释对象初始化函数体。
  • @Test:是junit对方法的注释,表明测试时会执行该方法。

对于纯xml和纯注解的选择(*)

说明:(建议)

  • 纯xml:组件多时,其稍显复杂和庞大。
  • 纯注解:有时候更加麻烦,比如为了不在配置类中写死数据库这些资料,需要设置properties属性来进行辅助
  • 混合:自己写的类用注解更方便,对于jar包中的一些类,采用xml写更省事。两者混合比较好。当然在公司允许个人选择的情况下。

注入数据

注解:

bean类型注入

  • @Autowired自动按照类型注入,用于注入数据功能。

  • @Qualifier:只能与@Autowired注解搭配,不能单独使用

    用于直接指定注入的类型:value=beanId(key)

    @Qulifies(value="Class的BeanId")

  • @Resource:相当于前两个配合的功能;

    用于类型注入,并直接指定注入的类型。@Resource(name="Id")

    • 注意,如果@Resource注解无法识别,是因为jdk11之后完全移除了javax拓展,而不能使用。导入java依赖即可。

      
          javax.annotation
          javax.annotation-api
          1.3.2
      
      

基本数据类型注入:

  • @Value:用于指定数据的值。同时可以使用Spring的spEL表达式:${表达式}。

集合数据类型注入:只能通过xml配置实现。

注解位置:放在类中需要初始化的成员变量前面。

当配置文件.xml中,已经将该类所属包放入了组件扫描标签内之后,就不需要多余配置了。

此时Spring框架会在Core Container核心容器中寻找配对,如:

@Component
public class ...
    @Autowired
    private AccountDAO dao;

核心容器中,存放着Map类型,当单独使用@Autowired自动注解 注入时,查找顺序:

  • ①在容器中寻找Object匹配的对象:如上述注解,如果找到一个AccountDAO及其父类或实现接口,且仅有唯一一个,则会将该对象注入数据,初始化dao。
  • ②如果找不到匹配的Object对象:虽然不会报错,但如果该类被使用,并且要使用该成员变量时,会出现空指针异常。
  • ③存在多个匹配的Object对象:此时会根据变量名和容器中组件的id进行匹配,如果相匹配,则返回该对象。否则报错,存在多个可匹配的对象。

改变作用范围

注解

@Scope:仅此一个注解,用于改变作用范围。其取值与之前描述的在bean标签内部的scope属性一样。写在类体前。


生命周期相关

注解:

  • @PostConstruct:用于指定初始化方法,放在类里的初始化方法前。
  • @PreDestroy:用于指定销毁方法,放在类里的销毁方法前。

Spring集成Junit

为什么要集成:

0.jpg

怎么集成:

  • @RunWith:这是Junit提供的注释,使用其,属性中填写JUnit专门为Spring提供的Runner类,即可将main方法替换成Spring的。
  • @ContextConfiguration:指定Spring的配置文件(填入:classpath = bean.xml或者classes = config.class)。此时Spring的核心容器已经被创建(应该是被RunWith创建),可以直接在Test类中对成员变量进行数据注入。

导入的依赖:spring-test:其他与spring-context一样。

注意: SpringJUnit4ClassRunner requires JUnit 4.12 or higher.

1.jpg

AOP

说明:
Aspect Oriented Programming,面向切面编程。通过预编译和运行期动态代理实现程序功能统一维护的一种技术。利用AOP可以对业务逻辑各个部分进行隔离,从而使业务逻辑耦合度降低。提高程序的可重用性,和提高开发的效率。

如当某类中,方法之间具有重复的代码段,此时可将其进行分离,从而简化代码,并且增强业务逻辑性。如ubuntu--eclipse--XmlIOC项目的BeanFactory类,实现了事务处理从业务层分离。

java动态代理知识

作用:

​ 在程序运行期间,不修改源码的方式对源码方法功能增强。(拓展开放修改关闭)

优势:

    - 减少重复代码
    - 提高开发效率
    - 维护方便。

Spring中的AOP

​ 利用配置的方法实现aop,精简代码。

相关术语

  • Joinpoint(连接点):在spring中指被代理类的所有方法,每一个都是连接点。

  • Pointcut(切入点):指被代理类中,被过滤增强的方法(连接点)。有的方法只是连接点,但是没有被过滤增强,就不是切入点。

  • Advice(通知):指功能为拦截切入点的方法,如Invoke();及在该方法中,调用切入点前后的方法。如事务处理中的开启事务、提交事务等都称为通知。

    • 环绕通知:指invoke这样的代理方法。
    • 前置通知:指invoke里面,在调用切入点之前的方法。如开启事务。
    • 后置通知:指invoke里面,在调用切入点之后的方法。如提交事务。
    • 异常通知:指invoke里面,catch语句中的方法。如回滚事务。
    • 最终通知:指invoke里面,finally语句中的方法。如断开数据库连接。
  • introduction(引介):引介是一种特殊的通知,在不修改源码的前提下,用于运行期增加一个方法或字段。

  • Target(目标对象):被代理对象

  • Weaving(织入):实现功能增强的过程,如spring动态运行时,在切入点前面织入前置通知。spring采用动态代理织入,而AspectJ采用编译期织入,和类加载期织入的方法。

  • Proxy(代理):一个被代理对象,经过织入增强后,返回的结果代理类。使用代理对象调用方法,可以实现增强后的功能。

  • Aspect(切面):切入点pointcut和通知、引介的结合称为切面。

    ​ 在spring配置时,必须要说清楚切面中的执行步骤、执行条件等。

开发阶段(our jobs)

可以有如下步骤:

  • 编写核心业务代码。
  • 提取公共代码,制作成通知。
  • 在spring中配置文件中,声明切入点与通知的关系(描述切面)。

运行阶段(Spring 框架完成)

  • 监控切入点方法。
  • 当切入点方法被调用时,动态创建目标对象的代理对象,并根据配置文件,织入通知、引介等。
  • 调用代理对象方法。

Maven依赖配置文件.xml

    ...
    
        org.springframework
        spring-context
        5.2.5.RELEASE
    
    
    
        org.aspectj
        aspectjweaver
        1.9.5
    
    ...

Spring配置文件.xml

​ 首先要导入xmlns:aop的命名空间:可以参考Springframework Core 里面的代码。

​ 然后是将业务类和通知类(包含增强方法的类)当做组件放入核心容器中。

​ 最后将业务类和通知类,进行切面配置。

​ 如下:




    
    
    
    
        
                        
        
    

AOP标签:(基于xml的AOP配置)

  • :aop配置主标签,里面包含多个切面(通知类)。

  • :配置切面标签。

    ​ 属性:

    • id:标识唯一切面。
    • ref:索引通知所属类。
  • :用于aspect内部,标识通知方法相对切入点的位置。依次为,前置、后置、异常、最终。

    ​ 属性:

    • method:通知类里的方法。
    • pointcut:其值为切入点表达式"execution( 访问修饰符 返回值 包名.包名...类名.方法名(参数) )"
    • pointcut-ref:其值为切入点id索引。
  • :用于aspect内部,用于标识该标签为环绕标签。其提供的功能与jdk动态代理Proxy中的调用处理对象的Invoke方法一样。

    • 功能:提供手动代码的方式,配置前置、后置、异常、最终通知。与Invoke的功能一样。

    • 属性与上述的通知标签一样。

    • 方法中:

      ​ 在其对应的通知方法中,应该要有切入点调用和完整的四种通知。Spring框架以该方法参数的方式提供了切入点方法的调用方法、参数列表等。

      ​ 通知方法参数:proceedingJoinPoint pjp

      ​ 通知方法内调用切入点方法:pjp.proceed(pjp.getArgs());

      ​ 通知方法内获取切入点参数:pjp.getArgs();

  • :用于标识切入点 、切入点表达式。

    属性:

    • id:用于标识切入点。
    • expression:用于输入切入点表达式。

    说明:该标签可以用于子标签或者子标签,是谁的子标签,其可被引用范围即是哪个的范围。

切入点表达式全通配写法execution(* *..*.*(..)); (对所有类的所有方法都加入切面配置)

​ 方法:

​ 写法说明:

  • ①省略了访问修饰符。(访问修饰符
  • ②任意返回值类型。( 方法返回值
  • ③任意包路径。(包名路径):用*代替包名,有几层包就写几个 *:如三层: *.*.*;或者用 ’*..Sevice‘ 表示:任意包下面的Service类
  • ④任意类。(类名):在类名后加.*(..)可以代表该类的所有方法。也可以输入指定参数,只允许该参数方法被过滤。

总说:类名、包名、方法名都可以用*代替;用..代替所有种类参数(如果用*代替则必须有且只有一个参数);

  • 实际开发中切入点表达式通常写法:业务层实现类下的所有方法,如:

    pointcut=execution(* com.yinghuo.springAOP.service.impl.*.*(..))

基于注释的AOP配置

xml配置+注释

  • xml中:

    • ①context的命名空间配置好,然后加入标签进行组件扫描。
    • ②用标签声明开启spring注解aop的支持:
  • 代码注释:

    • ①将所有相关类---被代理类、通知所属类全部加入注释。下述是通知所属类的注释,别忘记@Aspect

      @Component
      @Aspect
      public class Handler{
      ...
      }
      
    • ②在代理类中,创建一个切入点表达式函数。

      //类中
      @Pointcut("execution(* *..*.*(..))")
      public void pct(){}
      
    • ③注释所有通知方法

      @Before("pct()")                //其他通知类似
      public void beforeService() {
      ...
      }
      @Around("pct()")
      public Object proxyService(ProceedingJoinPoint pjp){
      ...
      } 
      

Spring中的JdbcTemplate

说明:对jdbc进行浅层封装。

功能:提供基础数据库交互。

步骤:

​ ①生成DataSource,可以用spring提供的DriverManagerDataSource类。需要参数:驱动器、url、name、password。

​ ②创建JdbcTemplate类,将上面的DriverManagerDataSource作为参数,而生成。

以上都可以使用springIOC的方式生成。

常用方法:

JdbcTemplate jdbcTemp = new JdbcTemplate(DataSource ds);

//无返回结果的执行语句
void jdbctemp.execute("mySql 语句");
   
//不设条件的查询
List JdbcTemp.query("mysql语句",new BeanPropertyRowMapper(被代理类.class));

//有条件查询
List JdbcTemp.query("mysql语句",new BeanPropertyRowMapper(被代理类.class),args...);

spring整合重复代码

​ spring用了一个JdbcDaoSupport抽象类整合了JdbcTemplateDriverManagerDataSource类,我们可以通过继承它,而减少重复代码。但这种情况下,只能使用xml配置bean,而不能用注释了。


Spring提供的事务控制

说明:之前我们自己编写事务控制,并用Spring提供的AOP进行动态代理。但Spring自己也有事务控制,我们只需要配置即可使用。

xml实现

步骤:(可以在后面的bean.xml配置代码中找到)

  • ①创建事务管理器
  • ②创建事务通知
  • ③创建切入点表达式
  • ④建立切入点和通知的联系
  • ⑤配置事务属性。

依赖

    
        org.springframework
        spring-tx
        5.2.5.RELEASE
    
    
        org.springframework
        spring-jdbc
        5.2.5.RELEASE    
    
    
        org.aspectj
        aspectjweaver
        1.9.5
    

相关类介绍

  • PlatFormTransactionManager接口:
    • DataSourceTransactionManager:常用的实现类。使用spring jdbc(JdbcTemplate)或iBatis实现。全限定名:org.springframework.djbc.datasource.DataSourceTransactionMAnager
    • HibernateTransactionManager:使用Hibernate实现的事务管理。

xml文件配置



 
    
    
        
    
    
    
    
        
        
        
    
    
    
    
        
        
            
            
            
            
        
    
    
    
    
        
    
    
   
    
        
        
        
         
     

想看unbuntu--eclipse--AnnotationAOP项目

基于xml+注解的实现

步骤:

  • ①xml中,配置事务管理器
  • ②xml中,开启注释事务:
  • ③然后在被代理类前,注释:@Transactional(propagation=Propagation.REQUIRED,readOnly=falase)
  • ④上述设置是全局型的,对于需要其他属性的切入点方法,需在其方法前注释③中同样的代码,只是属性不同。

java动态代理知识

可以看Ubuntu--eclipse--dynamicProxyStudy项目

可以使用java提供的Proxy类,来对一个基于接口的动态代理。(若是普通类,则需要使用基于子类的动态代理)

final Producer producer = new Producer();
ProducerInterface proxyProducer = (ProducerInterface)Proxy.newProxyInstance
    (producer.getClass().getClassLoader(),producer.getClass().getInterfaces(),
            new InvocationHandler() {...}
     )

如上,Producer是我自定义的类,其实现了ProducerInterface接口;

而proxyProducer是一个实现了对producer对象动态代理的类。通过Proxy.newProxyInstance()方法返回值(对象)得到。

以后可利用该proxyProducer对象来替代produce对象执行操作,并进行过滤处理。

newProxyInstance()方法的参数:

  • ①对象的类加载器。
  • ②对象的接口。
  • ③匿名内部类,来动态地生成一个处理类,其要求实现Invoke(Object proxy, Method method, Object[] args)方法:在里面进行过滤处理,并返回一个对象。

导入外部依赖包cglib,来实现基于子类的动态代理。

    
        cglib
        cglib
        2.2.2
    
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), 
    new MethodInterceptor() {
        /**
        * 
        * @param proxy
        * @param method
        * @param args
        * 以上三个和基于接口的动态代理的invoke方法一样
        * @param methodProxy:当前执行方法的代理对象。
        * @return
        * @throws Throwable
        */
        public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
                //对方法调用进行过滤
                float remainder = (Float)args[0];
                
                Object returnObj = null;
                if(method.getName().equals("saleProduct")) {
                    returnObj = method.invoke(producer, remainder*0.8f);
                }
                return returnObj;
        }       
    });

总说:像给对象增加事务支持等全方法步骤相同的功能时,可以使用动态代理类来处理,能够使服务类代码简洁,而且减少服务类编写代码时间。非常有用。

缺点:配置文件中需要配置的更复杂,如基于接口的代理类,和被代理类是同一个类型,类型注入时就不能使用@Autowired而是要指定名字。使用AOP可以解决这种麻烦。


异常收集

org.springframework.beans.factory.BeanNotOfRequiredTypeException

错误原因:是因为spring默认使用jdk的基于接口的动态代理,所以类型必须为接口类,当然spring注入时,会注入其实现类,但是接收类必须是接口类。

说明:当在测试类里执行时ubuntu--eclipse--AnnotationAOP中的testFind时,出现了如下。

经测试,当在其他main类中,使用如下代码是可以成功运行并实现过滤增强方法:

ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");   
ServiceInterface s = ac.getBean("Service",ServiceInterface.class);
System.out.println(s.findAccountById(3));   

而当将上述中的ServiceInterface改成实现类Service时,却出现同样的错误。

当我将在测试类中的接收类,private Service s改成private ServiceInterface s时,就成功了。?

你可能感兴趣的:(Spring 学习)