Spring注解开发之Spring常用注解

https://blog.csdn.net/Adrian_Dai/article/details/80287557
主要的注解使用:

本文用的Spring源码是4.3.16
@Configuration

此注解的作用是告诉Spring,添加该注解的类是配置类。


@ComponentScan

相信大家看到这个组合单词就知道是有什么作用了,扫描用的。直接看看源码吧

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
     
        @AliasFor("basePackages")
        String[] value() default {};
     
        @AliasFor("value")
        String[] basePackages() default {};
     
        Class<?>[] basePackageClasses() default {};
     
        Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
     
        Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
     
        ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
     
        String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
     
        boolean useDefaultFilters() default true;
     
        Filter[] includeFilters() default {};
     
        Filter[] excludeFilters() default {};
     
        boolean lazyInit() default false;
     
        @Retention(RetentionPolicy.RUNTIME)
        @Target({})
        @interface Filter {
     
            FilterType type() default FilterType.ANNOTATION;
     
            @AliasFor("classes")
            Class<?>[] value() default {};
     
            @AliasFor("value")
            Class<?>[] classes() default {};
     
            String[] pattern() default {};
     
        }
     
    }

由于不想篇幅过长,我把注释都去掉了,如果需要完整的Spring源码可以评论留下邮箱!

这边我主要讲一下我常用到的注释,其他注释读者可以自行学习。

value  指定要扫描的包。

==================================================简单的分隔符吧。下面一样

includeFilters 指定扫描的时候只需要包含哪些组件;

excludeFilters 指定扫描的时候按照什么规则排除那些组件;

它们两个都是一个Filter[],在上面的代码中也有,Filter中有个FilterType,它是一个枚举类,有5种类型。

FilterType.ANNOTATION:按照注解
FilterType.ASSIGNABLE_TYPE:按照给定的类型;
FilterType.ASPECTJ:使用ASPECTJ表达式
FilterType.REGEX:使用正则指定
FilterType.CUSTOM:使用自定义规则

来个示例吧:

    @ComponentScan(value="com.csdn.dh",includeFilters = {
                            @Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
                            @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={UserService.class}),
                            @Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
                    }

Controller.class是包含@Controller的注解的类,还有是UserService的类,然后最后一个是自定义的规则类,自定义规则类需要实现TypeFilter接口,实现match()方法;

boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException;

match 方法上的两个参数分别是:①metadataReader:读取到的当前正在扫描的类的信息

②metadataReaderFactory:可以获取到其他任何类信息的

留意到的读者会发现方法的返回值是boolean类型的,说明返回true就包含该组件,反之排除。我们来个尝试吧。看一下下面的代码和结果吧!

    @Configuration
    @ComponentScans({
        @ComponentScan(value = "com.csdn.dh",
                excludeFilters = {@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})}
        )
    })
    public class MainConfig {
    }

    public class MyTypeFilter implements TypeFilter {
        /**
         * metadataReader:读取到的当前正在扫描的类的信息
         * metadataReaderFactory:可以获取到其他任何类信息的
         */
        @Override
        public boolean match(MetadataReader metadataReader,
                MetadataReaderFactory metadataReaderFactory) throws IOException {
            //获取当前类注解的信息
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            //获取当前正在扫描的类的类信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            //获取当前类资源(类的路径)
            Resource resource = metadataReader.getResource();
            
            //className是全类名  com.csdn.dh.pojo.User
            String className = classMetadata.getClassName();
            System.out.println(className);
            if (className.contains("Pojo"))
                return true;
            return false;
        }
    }

    public class TestMain {
        public static void main(String[] args) {
            ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
            String[] beanNames = ac.getBeanDefinitionNames();
            for (String name : beanNames) {
                System.out.println("---->" + name);
            }
        }
    }

输出的结果是:

    com.csdn.dh.Filter.MyTypeFilter
    com.csdn.dh.pojo.User
    com.csdn.dh.pojo.UserPojo
    com.csdn.dh.test.TestMain
     
     
    ---->org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    ---->org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    ---->org.springframework.context.annotation.internalRequiredAnnotationProcessor
    ---->org.springframework.context.annotation.internalCommonAnnotationProcessor
    ---->org.springframework.context.event.internalEventListenerProcessor
    ---->org.springframework.context.event.internalEventListenerFactory
    ---->mainConfig
    ---->user

从结果中可以看到,UserPojo类已经被排除了。就是定义的MyTypeFilter起的作用。其他剩下的4个类型读者有兴趣的可自行尝试。

==================================================================

useDefaultFilters  这个属性是指是否自动扫描带有@Component、@Repository、@Service、@Controller注解的类,默认是true,是开启的。


@Scpoe

用于调整作用域,默认是singleton,单例的。来看一下源码里面怎么说吧:

            /**
         * Specifies the name of the scope to use for the annotated component/bean.
         * <p>Defaults to an empty string ({@code ""}) which implies
         * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
         * @since 4.2
         * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
         * @see ConfigurableBeanFactory#SCOPE_SINGLETON
         * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
         * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
         * @see #value
         */
        @AliasFor("value")
        String scopeName() default "";

看着是有4种可选的

prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象;

singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。以后每次获取就是直接从容器(map.get())中拿,

request:同一次请求创建一个实例

session:同一个session创建一个实例


@Lazy

懒加载:
    单实例的Bean:默认在容器启动的时候创建对象
    懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化


@Conditional

按照一定的条件进行判断,给容器中注册满组条件的Bean

来看一下源码:看一下他的value,我们需要自定义自己的条件,实现Condition,并重写matches方法,是不是眼熟这个方法?没错,刚才我们就写过了是吧?方法的意思

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Conditional {
     
        /**
         * All {@link Condition}s that must {@linkplain Condition#matches match}
         * in order for the component to be registered.
         */
        Class<? extends Condition>[] value();
     
    }

    public class MyCondition implements Condition {
        /**
         * ConditionContext:判断条件能使用的上下文(环境)
         * AnnotatedTypeMetadata:注释信息
         */
        @Override
        public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {
            //获取到容器使用的beanfactory
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            //获取类加载器
            ClassLoader classLoader = context.getClassLoader();
            //获取当前环境信息
            Environment environment = context.getEnvironment();
            //获取到Bean定义的注册类
            BeanDefinitionRegistry registry = context.getRegistry();
            return true;
        }
    }


具体的例子我就不写上,相信认真的读者也会方法跟刚才的TypeFilter的实现是差不多的。有兴趣读者可以看看这个两个参数的具体一些方法,来实现一下满足自己设定条件的Bean吧。


@Import

快速给容器中导入一个组件。看一下源码:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Import {
        /**
         * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
         * or regular component classes to import.
         */
        Class<?>[] value();
    }

属性是Class<?>的数组,这个注解的作用是导入一个组件,导入的组件默认是类的全类名,即@Import(User.class)    输出的BeanName是com.csdn.dh.pojo.User

认真看源码的读者能发现,它还能用ImportSelector和ImportBeanDefinitionRegistrar

我们可以自定义需要返回的组件:

    public class MyImportSelector implements ImportSelector {
        //返回的String数组就是到导入到容器中的组件全类名
        //AnnotationMetadata:当前标注@Import注解的类的所有注解信息
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            //方法不要返回null值,否则会报空指针异常(读者可以自行debug看一下原因)
            return new String[]{"com.csdn.dh.pojo.User"};
        }
    }

    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        /**
         * AnnotationMetadata:当前类的注解信息
         * BeanDefinitionRegistry:BeanDefinition注册类;
         *         把所有需要添加到容器中的bean;调用BeanDefinitionRegistry.registerBeanDefinition手工注册进来
         */
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            //指定Bean定义信息;(Bean的类型)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class);
            //注册一个Bean,指定bean名
            registry.registerBeanDefinition("user", beanDefinition);
            }
        }
    }


@Profile

指定组件在哪个环境的情况下才能被注册到容器中,不指定则在任何环境下都能注册这个组件。

①加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
②写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
③没有标注环境标识的bean在任何环境下都是加载的;

    @PropertySource("classpath:/db.properties")
    @Configuration
    public class TestDHProfile implements EmbeddedValueResolverAware{
        @Value("${db.user}")
        private String user;
        
        private StringValueResolver resolver;
        private String  driverClass;
        
        @Profile("test")
        @Bean("testDataSource")
        public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setUser(user);
            dataSource.setPassword(pwd);
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
            dataSource.setDriverClass(driverClass);
            return dataSource;
        }
        
        @Profile("dev")
        @Bean("devDataSource")
        public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setUser(user);
            dataSource.setPassword(pwd);
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
            dataSource.setDriverClass(driverClass);
            return dataSource;
        }
     
        @Override
        public void setEmbeddedValueResolver(StringValueResolver resolver) {
            this.resolver = resolver;
            driverClass = resolver.resolveStringValue("${db.driverClass}");
        }
     
    }

    public class TestProfileMain {
        //1、使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test
        //2、代码的方式激活某种环境;
        @Test
        public void testMain(){
            //1、创建一个applicationContext
            AnnotationConfigApplicationContext applicationContext =
                    new AnnotationConfigApplicationContext();
            //2、设置需要激活的环境
            applicationContext.getEnvironment().setActiveProfiles("dev");
            //3、注册主配置类
            applicationContext.register(TestDHProfile.class);
            //4、启动刷新容器
            applicationContext.refresh();
            applicationContext.close();
        }
    }

相信认真看的读者也看到上面TestDHProfile中实现了EmbeddedValueResolverAware的接口。这边我说一下这个接口的作用吧,实现这个接口的setEmbeddedValueResolver方法可以读取配置文件,拿到配置文件中属性对应的值,如我代码中的driverClass就是利用resolver来获取的。

@Value

这个注解不讲了。


@Autowired

自动注入。

    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Autowired {
        /**
         * Declares whether the annotated dependency is required.
         * <p>Defaults to {@code true}.
         */
        boolean required() default true;
    }

(1)、[标注在方法位置]:参数从容器中获取,默认不写@Autowired效果是一样的,都能自动装配
(2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
(3)、放在参数位置:默认按照类型去容器查找对应组件,如果找到多个相同类型的会按照组件的id到容器中查找。

注意:自动注入一定要将属性值赋好值,否则会报错。因为注解中required默认是true的,即必须的。可以进行修改。
@Qualifier

使用@Qualifier指定需要装配的组件的id。

例如:当你有两个相同类型的service时,为@Service("userService1"),@Service("userService2");然后你

@Autowired注解了private UserService userService;这样子Spring不知道你需要哪个service,所以你可以在userService上加上@Qualifier("userService1")与自动注入搭配使用。
@Primary

让Spring进行自动装配的时候,默认使用首选的Bean。这个不说了,不过这个注解是加到Bean上的


@Resource

可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;

但是没有能支持@Primary功能没有支持@Autowired(reqiured=false);


@Inject
需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;
---------------------
作者:Adrian_Dai
来源:CSDN
原文:https://blog.csdn.net/adrian_dai/article/details/80287557
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2018-11-05 10:59  jimcsharp  阅读(383)  评论(0编辑  收藏  举报