Contents

SSM系列(二)---Spring 常用注解分析

本文主要对 Spring 框架中经常用到的注解与配置进行了说明。

更多文章欢迎访问我的个人博客–>幻境云图

1. Bean相关的注解

与SpringBean相关的注解有以下四大类:

  • @Controller :标注一个控制器组件类 Controller层
  • @Service:标注一个业务逻辑组件类 Service层
  • @Repository :标注一个 DAO 组件类 DAO层
  • @Component :标注一个普通的 Spring Bean 类 前面三个都不是但又想交给Spring管理就用这个

2. @Autowired与@Resource区别

2.1 相同点

@Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。

2.2 不同点

1. 提供方

@AutowiredSpring 提供的注解;

@ResourceJ2EE提供的注解,javax.annotation 包下的注解,来自于JSR-250,需要JDK1.6及以上。

2. 注入方式

@Autowired只按照Type 注入;

@Resource 默认按Name自动注入,也提供按照Type 注入;

3. 属性

@Autowired注解可用于为类的属性、构造器、方法进行注值。

默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其 required 属性为false。 @Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合 @Qualifier 注解进行限定,指定注入的bean名称。

@Resource有两个中重要的属性:nametype

name 属性指定 byName,如果没有指定 name 属性:

当注解标注在字段上,即默认取字段的名称作为 bean 名称寻找依赖对象, 当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。 @Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。

4. 其他

@Autowired注解进行装配容易抛出异常,特别是装配的 bean 类型有多个的时候,解决的办法是增加 @Qualifier 注解进行限定。

@Resource注解的使用性更为灵活,可指定名称,也可以指定类型;

3. context:annotation-config与context:component-scan

3.1 context:annotation-config

我们一般在含有 Spring 的项目中,可能会看到配置项中包含这个配置节点

<context:annotation-config>

这条配置会向 Spring 容器中注册以下4个 BeanPostProcessor

  • AutowiredAnnotationBeanPostProcessor

  • CommonAnnotationBeanPostProcessor

  • PersistenceAnnotationBeanPostProcessor

  • RequiredAnnotationBeanPostProcessor

注册这4个 BeanPostProcessor 的作用,就是为了你的系统能够识别相应的注解

如果想使用 @Resource@PostConstruct@PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor。 如果想使用 @PersistenceContext注解,就必须声明PersistenceAnnotationBeanPostProcessor的Bean。 如果想使用 @Autowired注解,那么就必须声明AutowiredAnnotationBeanPostProcessor的 Bean。 如果想使用 @Required的注解,就必须声明RequiredAnnotationBeanPostProcessor的Bean。

所以如果不加一句context:annotation-config那么上面的这些注解就无法识别

3.2 context:component-scan

context:component-scan包括了context:annotation-config的功能,即注册 BeanPostProcessor 使系统能够识别上面的注解。

同时还会自动扫描所配置的包下的 bean。即 扫描包下面有@Controller@Service@Repository@Component这四个注解的类,自动放入 Spring 容器。

所以一般写context:component-scan就行了。

3. 实例演示

就拿前面的 student 和 book 举例 实体类这样写,使用注解进行属性注入

public class Student {
    @Value(value = "illusory")
    private String name;
    @Value(value = "23")
    private int age;
    @Autowired
    private Book book;
}
public class Book {
    @Value(value = "defaultType")
    private String type;
    @Value(value = "defaultName")
    private String name;
}

配置文件就不用写各种 property 属性注入了。

<property name="name" value="illusory"></property>

使用@Autowired后也不用配置引用对象了。

<property name="book" ref="book"></property>

但是还是需要在 xml配置 bean 的基本信息

  <bean id="student" class="spring.Student"></bean>
  <bean id="book" class="spring.Book"></bean>
   <context:annotation-config />

如果在实体类加上@Component注解

@Component(value = "student")
public class Student {
    @Value(value = "illusory")
    private String name;
    @Value(value = "23")
    private int age;
    @Autowired
    private Book book;
}

就不用在xml中配置bean了,只需要在xml中配置

<context:component-scan base-package="spring"/>

系统可以识别到前面的注解,同时还会自动扫描包下的 bean。

这样xml中只要要一行就搞定了。

4. 自定义初始化与销毁方法

init-method destroy-method属性对应的注解

  • @PostConstruct注解,在对象创建后调用
  • @PreDestroy注解,在对象销毁前调用
    @PostConstruct
    public void init() {
        System.out.println("init");
    }

    @PreDestroy
    public void destory() {
        System.out.println("destory");
    }

5. @Component和@Configuration 作为配置类的区别

5.1 概述

@Component@Configuration都可以作为配置类,但还是有一定差别的。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component  //看这里!!!
public @interface Configuration {
    String value() default "";

Spring 中新的 Java 配置支持的核心就是 @Configuration 注解的类

这些类主要包括 @Bean 注解的方法来为 Spring 的 IoC 容器管理的对象定义实例,配置和初始化逻辑。

使用 @Configuration 来注解类表示类可以被 Spring 的 IoC 容器所使用,作为 bean 定义的资源。

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

这和 Spring 的 XML 文件中的非常类似

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

5.2 实例演示

@Configuration
public static class Config {

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }

    @Bean
    public SimpleBeanConsumer simpleBeanConsumer() {
        return new SimpleBeanConsumer(simpleBean());
    }
}

@Component
public static class Config {

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }

    @Bean
    public SimpleBeanConsumer simpleBeanConsumer() {
        return new SimpleBeanConsumer(simpleBean());
    }
}

第一个代码正常工作,正如预期的那样,SimpleBeanConsumer 将会得到一个单例 SimpleBean 的链接。 第二个配置是完全错误的,虽然 Spring 会创建一个 SimpleBean 的单例bean,但是 SimpleBeanConsumer 将获得另一个SimpleBean实例(也就是相当于直接调用new SimpleBean() , 这个bean是不归Spring管理的)。

5.3 原因

使用 @Configuration 所有标记为 @Bean的方法将被包装成一个 CGLIB包装器,它的工作方式就好像是这个方法的第一个调用,那么原始方法的主体将被执行,最终的对象将在 Spring上下文中注册。所有进一步的调用只返回从上下文检索的 bean。

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            //判断是否被@Configuration标注
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
                if (!(beanDef instanceof AbstractBeanDefinition)) {
                    throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                            beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
                }
                else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
                    logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
                            "' since its singleton instance has been created too early. The typical cause " +
                            "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                            "return type: Consider declaring such methods as 'static'.");
                }
                configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
            }
        }
        if (configBeanDefs.isEmpty()) {
            // nothing to enhance -> return immediately
            return;
        }
        ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
        for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            AbstractBeanDefinition beanDef = entry.getValue();
            // If a @Configuration class gets proxied, always proxy the target class
            beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            try {
                // Set enhanced subclass of the user-specified bean class
                Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
                //生成代理的class
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
                if (configClass != enhancedClass) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                    }
                    //替换class,将原来的替换为CGLIB代理的class
                    beanDef.setBeanClass(enhancedClass);
                }
            }
            catch (Throwable ex) {
                throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
        }
    }

isFullConfigurationClass代码如下:

//是否为配置类
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata));
}

//是否为完整配置类
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
return metadata.isAnnotated(Configuration.class.getName());
}
//是否为精简配置类
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
    // Do not consider an interface or an annotation...
    if (metadata.isInterface()) {
        return false;
    }

    // Any of the typical annotations found?
    for (String indicator : candidateIndicators) {
        if (metadata.isAnnotated(indicator)) {
            return true;
        }
    }

    // Finally, let's look for @Bean methods...
    try {
        return metadata.hasAnnotatedMethods(Bean.class.getName());
    }
    catch (Throwable ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
        }
        return false;
    }
}
//精简配置类包含的注解
static {
    candidateIndicators.add(Component.class.getName());
    candidateIndicators.add(ComponentScan.class.getName());
    candidateIndicators.add(Import.class.getName());
    candidateIndicators.add(ImportResource.class.getName());
}

6. 参考

https://blog.csdn.net/long476964/article/details/80626930

https://www.jianshu.com/p/89f55286cf21