spring应用手册 IOC(注解方式配置)第一部分

162 阅读10分钟

spring应用手册

2、IOC(注解实现)

2.1注解实现spring的Helloworld

[1]创建maven项目,添加依赖:

 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.1.3.RELEASE</version>
 </dependency>

[2]准备两个DAO接口和实现类:

接口:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public interface IAutorDAO {
     public String get();
 }
 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public interface IArticleDAO {
     public int save(String title);
 }

实现类:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public class ArticleDAO implements IArticleDAO {
     @Override
     public int save(String title) {
         System.out.println("ArticleDAO-save->保存文章:"+title);
         return 1;
     }
 }
 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public class AuthorDAO  implements IAutorDAO {
     @Override
     public String get() {
         return "戴着假发的程序员";
     }
 }

[3]准备ArticleService

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public class ArticleService {
     private IAutorDAO autorDAO;
     private IArticleDAO articleDAO;
 ​
     public void setAutorDAO(IAutorDAO autorDAO) {
         this.autorDAO = autorDAO;
     }
 ​
     public void setArticleDAO(IArticleDAO articleDAO) {
         this.articleDAO = articleDAO;
     }
 ​
     public int save(String title){
         String author = autorDAO.get();
         System.out.println("ArticleService-save:");
         System.out.println("author:"+author);
         return articleDAO.save(title);
     };
 }

[4]添加一个配置类,并且在配置类中使用@Bean的方式配置上面的是三个类:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Configuration
 public class AppConfig {
     //配置 ArticleDAO对象
     @Bean
     public IArticleDAO articleDAO(){
         return new ArticleDAO();
     }
     //配置 AuthorDAO
     @Bean
     public IAutorDAO authorDAO(){
         return new AuthorDAO();
     }
     //配置ArticleService对象
     @Bean
     public ArticleService articleService(){
         ArticleService articleService = new ArticleService();
         //注入对应的属性
         articleService.setArticleDAO(articleDAO());
         articleService.setAutorDAO(authorDAO());
         return articleService;
     }
 }

[5]测试:

@Test
public void testAnnotation(){
    ApplicationContext ac =
            new AnnotationConfigApplicationContext(AppConfig.class);
    ArticleService bean = ac.getBean(ArticleService.class);
    bean.save("spring应用手册");
}

1585736579924.png

2.2Hellowrold程序的解析

[1]@Configuration

@Configuration注解表示要给类为配置类。

我们在AppConfig类上方添加@Configuration注解,表示当前类是一个配置类。配置类本身也是spring容器中的一个元素,spring也会加载配置类,并且为其生成对应的BeanDefinition缓存。

一个配置类和一个配置文件是对等。

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Configuration
 public class AppConfig {
 }

相当于:

 <?xml version="1.0" encoding="UTF-8"?>
 <beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd">
 ​
 </beans>

你可以在配置类中按照注解的标准注解bean。

当然你可以像配置文件一样在你的工程中添加多个配置类。(多个配置类的使用参见后面的容器创建方式)

注意:@Configuration修饰的类不能是final的。理论上也不能是abstract修饰的。

[2]@Bean

@Bean注解的作用就是给spring容器中注册一个bean对象。

一个@Bean就类似于配置文件中的一个bean标签。(关于@bean的属性,参见后面的@Bean属性详解)

 //配置 ArticleDAO对象
 @Bean
 public IArticleDAO articleDAO(){
     return new ArticleDAO();
 }
 //相当于添加了如下的配置
 //<bean id="articleDAO" class="com.st.dao.IArticleDAO">

@Bean都是写在方法上方的,表示这个方法的返回值对象将会被添加到spring的容器中。很明显通过@Bean的方式添加的对象,创建方式是由我们自己定义的。

@Bean添加的对象在默认情况下,这个bean的id就是对应的方法名。 比如:IArticleDAO对象的id就是articleDAO.

注意:@Bean方法返回值如果是void,则不会进行类注册动作。 @Bean的方法当然也可以是static修饰的。

[3]容器初始化 我们可以像加载配置文件一样加载配置类完成容器创建:

 ApplicationContext ac =
         new AnnotationConfigApplicationContext(AppConfig.class);

很明显我们使用的ApplicationContext类的实现类不在是ClassPathXmlApplicationContext类。

这里我们使用AnnotationConfigApplicationContext类,传入的参数是配置类的Class对象。

当然我们还可以这样使用:

 public AnnotationConfigApplicationContext(org.springframework.beans.factory.support.DefaultListableBeanFactory beanFactory) { /* compiled code */ }
 ​
 public AnnotationConfigApplicationContext(java.lang.Class<?>... annotatedClasses) { /* compiled code */ }

我们可以在创建AnnotationConfigApplicationContext时传入不同的参数:

beanFactory:我们可以传入一个beanFactory对象(关于beanFactory参看beanFactory的详解)

class类型的可变参数:如果我们有多个配置类,我们可以传入一个或者多个配置类的class对象,例如:

 ApplicationContext ac =
         new AnnotationConfigApplicationContext(AppConfig.class, AppConfig1.class);

2.3@Bean的value和name属性

源码:

 @org.springframework.core.annotation.AliasFor("name")
 java.lang.String[] value() default {};
 ​
 @org.springframework.core.annotation.AliasFor("value")
 java.lang.String[] name() default {};

@Bean中的name和value属性 和 配置文件中的bean标签的name属性有同样的功能。

@Bean配置的类的默认id是方法的名称,但是我们可以通过value或者name给这个bean取别名。 注意:value和name属性不能并存。 而且如果配置了value或者name,那么我们将无法在通过方法名称获取这个bean了。

例如:

 //配置ArticleService对象
 @Bean(value="aservice")
 //@Bean(name="aservice")
 public ArticleService articleService(){
     ArticleService articleService = new ArticleService();
     //注入对应的属性
     articleService.setArticleDAO(articleDAO());
     articleService.setAutorDAO(authorDAO());
     return articleService;
 }

我们可以通过下面的方式获取service对象:

通过类型获取:

 ArticleService bean = ac.getBean(ArticleService.class);

通过方法名称获取:

如果配置了value或者name,那么下面的操作会失效

 ArticleService bean = (ArticleService) ac.getBean("articleService");

通过别名获取:

value和name配置别名,但是两个属性不能共存,也不能使用特殊符号,也不能同时配置多个别名

 ArticleService bean = (ArticleService) ac.getBean("aservice");

2.4@Bean的autowire属性

源码:

@java.lang.Deprecated
org.springframework.beans.factory.annotation.Autowire autowire() default org.springframework.beans.factory.annotation.Autowire.NO;

autowire和配置文件中的autowire有同样的作用。 表示自动组装方式。 通过读取源码我们发现spring5.x开始已经不建议使用了。

Autowrire的几个值:

No:不启用自动装配,这也是默认值。

byName: 通过属性的名字的方式查找JavaBean依赖的对象并为其注入。

byType:通过属性的类型查找JavaBean依赖的对象并为其注入。但是如果同一种类型出现多个bean就会出错。

constructor:和byType一样,也是通过类型查找依赖对象。但是是通过构造方法注入。spring5.x已经没有了

autodetect:在byType和constructor之间自动的选择注入方式。 spring5.x已经没有了

default:由上级标签beans的default-autowire属性确定。 spring 5.x已经没有了

我们可以测试一个byType配置:

我们将之前我们手动注入属性的代码注释,然后再进行测试:

     //配置ArticleService对象
     @Bean(name="aservice",autowire = Autowire.BY_TYPE)
     public ArticleService articleService(){
         ArticleService articleService = new ArticleService();
         //注入对应的属性
 //        articleService.setArticleDAO(articleDAO());
 //        articleService.setAutorDAO(authorDAO());
         return articleService;
     }

1585799775313.png

2.5@Bean的autowireCandidate属性

 boolean autowireCandidate() default true;

autowireCandidate和配置文件bean标签的autowireCandidate属性一样,就是让其他的bean在按照类型注入时,忽略当前的bean。 默认值true。

我们可以在添加一个ArticleDAO的实现类:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public class ArticleDAO_other implements IArticleDAO {
     @Override
     public int save(String title) {
         System.out.println("ArticleDAO_other-save->保存文章:"+title);
         return 1;
     }
 }

并且在AppConfig类中添加其注册信息:

 @Bean
 public static  IArticleDAO articleDAOOther(){
     return new ArticleDAO_other();
 }

注意:ArticleService的装配方式依然是byType

 @Bean(name="aservice",autowire = Autowire.BY_TYPE)
 public ArticleService articleService(){
     ArticleService articleService = new ArticleService();
     return articleService;
 }

这时我们再测试:报错:

 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'aservice' defined in com. st.dk.demo3.AppConfig: Unsatisfied dependency expressed through bean property 'articleDAO'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com. st.dk.demo3.dao.IArticleDAO' available: expected single matching bean but found 2: articleDAO,articleDAOOther

我们修改配置:

 @Bean(autowireCandidate = false)
 public static  IArticleDAO articleDAOOther(){
     return new ArticleDAO_other();
 }

测试会通过。

由于我们在articleDAOOther方法上方的@Bean中配置autowireCandidate为false,则spring在给ArticleService装配IArticleDAO时,就忽略了ArticleDAO_other,那么就只能找到一个ArticleDAO,那么自然就了。

注意:如果你是按照名称注入的,那么无论你是否配置autowireCandidate,都可以正常注入。

2.6@Bean的initMethod属性

 java.lang.String initMethod() default "";

initMethod属性通过bean标签中的initMethod属性一样,也是来配置实例化之后的初始化方法的。

参照spring的bean加载流程。

spring在创建bean对象之后就会调用initMethod指定的初始化回调方法。

案例:

我们修改ArticelDAO的程序:添加一个构造方法和一个初始化回调方法init。

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public class ArticleDAO implements IArticleDAO {
     public ArticleDAO(){
         System.out.println("创建ArticleDAO对象");
     }
     public void init(){
         System.out.println("执行ArticleDAO的初始化回调方法init");
     }
     @Override
     public int save(String title) {
         System.out.println("ArticleDAO-save->保存文章:"+title);
         return 1;
     }
 }

在ArticleDAO的@Bean注册方法中添加配置:

 //配置 ArticleDAO对象
 @Bean(initMethod = "init")
 public static IArticleDAO articleDAO(){
     return new ArticleDAO();
 }

在测试:

1585801854368.png 一旦创建容器就会创建对象,并且执行其初始化回调方法init。

2.7@Bean的destroyMethod属性

 java.lang.String destroyMethod() default "(inferred)";

destroyMethod和bean标签的destroyMethod属性一样,是用来配置释放资源的回调方法。一旦配置了,spring会在销毁这个bean之前调用这个释放资源的回调方法。

我们在ArticleDAO中添加方法:

 public void destroy(){
     System.out.println("执行ArticleDAO的资源释放回调方法destroy");
 }

修改配置,增加资源释放的回调方法配置:

 //配置 ArticleDAO对象
 @Bean(initMethod = "init",destroyMethod = "destroy")
 public static IArticleDAO articleDAO(){
     return new ArticleDAO();
 }

测试:

1585802109795.png 我们会看到,spring在销毁容器之前,会先销毁bean,销毁bean之前会先调用bean的释放资源的回调方法。

2.8 spring自动扫描bean

我们可以通过配置basePackages让spring自动扫面有spring标准注释(参见对应的标准注释)的bean。

查看AnnotationConfigApplicationContext构造方法:

 public AnnotationConfigApplicationContext(java.lang.String... basePackages) 

我们可以在创建AnnotationConfigApplicationContext对象时传入参数basePackage,当然这是一个可变参数,所以你同时可以传入多个basePackage。

spring会自动扫描你传入的包以及其子孙包下的所有类,查看这些类上方是否有spring的标准注释。如果有spring会根据注释完成bean的加载。

案例:

我们在com. st.dk.demo4包下创建如下结构:

1585804986792.png 在三个DAO的实现类上添加注解@Component (@Component注解详情请参看@Component的详情章节)。

我们以下面的方式创建spring容器:

 //创建spring容器,并且传入要扫面的包(这里可以传入多个)
 ApplicationContext ac =
         new AnnotationConfigApplicationContext("com.st.dk.demo4");

测试:

1585805132128.png

2.9 @Component注解注册Bean

@Component是spring的一个标准注解

 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 @org.springframework.stereotype.Indexed
 public @interface Component {
     java.lang.String value() default "";
 }

它的注册目标是TYPE,它的主要作用是将当前类标注为要注册在spring中的类。 那么spring在扫描这个类的时候就会对这个类进行加载,翻译,实例化。

案例:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class AuthorDAO  implements IAutorDAO {
     @Override
     public String get() {
         return "戴着假发的程序员";
     }
 }

当srping扫描AuthorDAO类时会对该类进行加载。

我们可以通过@Component的value属性配置该类的对象在spring容器中的id/name。默认情况下如果不配置,spring会自动为改类的bean生成一个bean,规则就是将当前类的简称类名的首字母小写生成一个字符串。

例如:authorDAO 我们可以通过下面的方式获取

 IAutorDAO autorDAO = (IAutorDAO) ac.getBean("authorDAO");

当然如果当前类的简称中如果前面连续两个大写字母,则spring就不会再将首字母小写了。

例如:原来的类名是:AUthorDAO,spring生成的name是:AUthorDAO。

我们也可以通过value属性制定name:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component("adao")
 public class AuthorDAO implements IAutorDAO  {
     @Override
     public String get() {
         return "戴着假发的程序员";
     }
 }

注意:你一旦制定了value,spring就不会再自动生成name。那么你如果通过name获取bean则要使用制定的name。当然如果你是通过类型获取的,就不需要担心这个问题了。

 IAutorDAO autorDAO = (IAutorDAO) ac.getBean("adao");

一般情况下我们都不会去修改这个value,但是如果在一个容器中出现了多个同类型的bean,我们就可以通过value指定name,方便我们通过name获取bean对象。

2.10@Repository、@Service和@Controller

这三个注解是我们在开发中经常用到的注解,我们来看看这些注解的源码。

 package org.springframework.stereotype;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 @org.springframework.stereotype.Component
 public @interface Repository {
     @org.springframework.core.annotation.AliasFor(annotation = org.springframework.stereotype.Component.class)
     java.lang.String value() default "";
 }
 package org.springframework.stereotype;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 @org.springframework.stereotype.Component
 public @interface Service {
     @org.springframework.core.annotation.AliasFor(annotation = org.springframework.stereotype.Component.class)
     java.lang.String value() default "";
 }
 package org.springframework.stereotype;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 @org.springframework.stereotype.Component
 public @interface Controller {
     @org.springframework.core.annotation.AliasFor(annotation = org.springframework.stereotype.Component.class)
     java.lang.String value() default "";
 }

通过查看源码,我发现这三个注解都“继承”自@org.springframework.stereotype.Component。而且各自都没有特殊的扩展和属性。 所以这三个注解的功能和@Component完全相同。 既然完全相同为什么要出现这三个注解呢?

主要是为了提高我们代码的可读性。 我们都知道在实际开发中我们都是通过包扫描和注解的方式完成bean注册。

那么为了方便我们标记各种bean的不同种类就有了这三个注解。

@Repository : 一般都是用来标记数据持久化的类。

@Service:一般都是用来标记业务处理的类。

@Controller:一般都是用来标记后端控制器的类(参照web模块)。

当然其实这四个注解是可以互换的。

未完待续...... 欢迎关注