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. 提供方
@Autowired 是 Spring 提供的注解;
@Resource是J2EE提供的注解,javax.annotation 包下的注解,来自于JSR-250,需要JDK1.6及以上。
2. 注入方式
@Autowired只按照Type 注入;
@Resource 默认按Name自动注入,也提供按照Type 注入;
3. 属性
@Autowired注解可用于为类的属性、构造器、方法进行注值。
默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其 required 属性为false。
@Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合 @Qualifier 注解进行限定,指定注入的bean名称。
@Resource有两个中重要的属性:name和type。
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