Spring 注解开发详解

1. 注解驱动入门案例介绍

1.1 需求描述

1.需求:
	实现保存一条数据到数据库。
2.表结构:
	create table account(
		id int primary key auto_increment,
		name varchar(50),
		money double(7,2)
	);
3.要求:
	使用spring框架中的JdbcTemplate和DriverManagerDataSource
	使用纯注解配置spring的ioc

1.2 案例实现

  • 导入坐标
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.45</version>
    </dependency>
</dependencies>
  • 编写配置类
/**
 * spring的配置类
 * 用于替代xml配置
 */
@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbc.properties")
public class SpringConfiguration {
}


/**
 * 连接数据库的配置
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;


    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }


    @Bean("dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
  • 编写配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_day01
jdbc.username=root
jdbc.password=1234
  • 编写测试类
public class SpringAnnotationDrivenTest {

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.根据id获取对象
        JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate",JdbcTemplate.class);
        //3.执行操作
        jdbcTemplate.update("insert into account(name,money)values(?,?)","test",12345);
    }
}

2. IOC的常用注解分析

2.1 @Configuration

2.1.1 源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	/**
	 * Explicitly specify the name of the Spring bean definition associated with the
	 * {@code @Configuration} class. If left unspecified (the common case), a bean
	 * name will be automatically generated.
	 * <p>The custom name applies only if the {@code @Configuration} class is picked
	 * up via component scanning or supplied directly to an
	 * {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
	 * is registered as a traditional XML bean definition, the name/id of the bean
	 * element will take precedence.
	 * @return the explicit component name, if any (or empty String otherwise)
	 * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";
}

2.1.2 说明

它是在spring3.0版本之后加入的。此注解是spring支持注解驱动开发的一个标志。表明当前类是spring的一个配置类,作用是替代spring的applicationContext.xml。但其本质就是@Component注解,被此注解修饰的类,也会被存入spring的ioc容器。

使用场景:
在注解驱动开发时,用于编写配置的类,通常可以使用此注解。一般情况下,我们的配置也会分为主从配置,@Configuration一般出现在主配置类上。值得注意的是,如果我们在注解驱动开发时,构建ioc容器使用的是传入字节码的构造函数,此注解可以省略。但是如果传入的是一个包,此注解则不能省略。

属性:
value: 用于存入spring的Ioc容器中Bean的id。

2.1.3 示例

在注解驱动的入门案例中,由于没有了applicationContext.xml,就没法在xml中配置spring创建容器要扫描的包了。那么,我们自己写的一些类,通过注解配置到ioc容器中也无法实现了。此时就可以使用此注解来代替spring的配置文件。

/**
 * spring的配置类
 * 用于替代xml配置
 */
@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbc.properties")
@ComponentScan("com.test")
public class SpringConfiguration {
}

2.2 @ComponentScan

2.2.1、源码

@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;


	/**
	 * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
	 * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {
		/**
		 */
		FilterType type() default FilterType.ANNOTATION;

		/**
		 */
		@AliasFor("classes")
		Class<?>[] value() default {};
      
		/**
		 */
		@AliasFor("value")
		Class<?>[] classes() default {};
      
		/**
		 */
		String[] pattern() default {};
	}
}

2.2.2 说明

  • 作用:
    用于指定创建容器时要扫描的包。该注解在指定扫描位置时,可以指定包名,也可以指定扫描类。同时支持定义扫描规则,例如包含哪些或者排除哪些。同时,它还支持自定义Bean的命名规则。

  • 属性:

    value: 用于指定要扫描的包。当指定包名称之后,spring会扫描指定包及其子包下的所有类。
    basePackages: 它和value作用是一样的。
    basePackageClasses: 指定具体要扫描的类的字节码。
    nameGenrator: 指定扫描bean对象存入容器时的命名规则。
    scopeResolver: 用于处理并转换检测到的Bean的作用范围。
    soperdProxy: 用于指定bean生成时的代理方式。默认是Default,则不使用代理。
    resourcePattern: 用于指定符合组件检测条件的类文件,默认是包扫描下的 **/*.class
    useDefaultFilters: 是否对带有@Component, @Repository, @Service, @Controller注解的类开启检测,默认是开启的。
    includeFilters: 自定义组件扫描的过滤规则,用以扫描组件。
    excludeFilters: 自定义组件扫描的排除规则。
    lazyInit: 组件扫描时是否采用懒加载 ,默认不开启。

  • 使用场景:

    在注解驱动开发时,我们自己编写的类都使用注解的方式进行配置,要想让spring添加到ioc容器中,就需要使用此注解来实现组件的扫描。

2.2.3 示例

在入门案例中,如果我们加入了dao或者记录日志的工具类,这些使用了@Component或其衍生注解配置的bean,要想让他们进入ioc容器,就需要使用@ComponentScan。

package com.test.dao.impl;
import com.test.dao.AccountDao;      

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao{
	//持久层开发
}      

/**
 * spring的配置类
 * 用于替代xml配置
 */
@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbc.properties")
@ComponentScan("com.test")
public class SpringConfiguration {
}

2.3 @Bean

2.3.1 源码

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
	/**
	 */
	@AliasFor("name")
	String[] value() default {};
  
	/**
	 */
	@AliasFor("value")
	String[] name() default {};

  	/**
	 */
	@Deprecated
	Autowire autowire() default Autowire.NO;

  	/**
	 */
	boolean autowireCandidate() default true;

  	/**
	 */
	String initMethod() default "";

  	/**
	 */
	String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}	

2.3.2 说明

  • 作用:

    它写在方法上,表示把当前方法的返回值存入spring的ioc容器。同时还可以出现在注解上,作为元注解来使用。

  • 属性:
    name: 用于指定存入spring容器中bean的标识。支持指定多个标识。当不指定该属性时,默认值是当前方法的名称。
    value: 它和name属性的作用是一样的。
    autowireCandidate: 用于指定是否支持自动按类型注入到其他bean中。只影响@Autowired注解的使用,不影响@Resource注解注入。默认值为true,意为允许使用自动按类型注入。
    initMethod: 用于指定初始化方法。
    destroyMethod: 用于指定销毁方法。

  • 使用场景:

    通常情况下,在基于注解的配置中,我们用于把一个类存入spring的ioc容器中,首先考虑的是使用@Component以及他的衍生注解。但是如果遇到要存入容器的Bean对象不是我们写的类,此时无法在类上添加@Component注解,这时就需要此注解了。

2.3.3 示例:

例如,在我们配置JdbcTemplate使用Spring内置数据源DriverManagerDataSource时,数据源类是spring-jdbc这个jar包中类,此时我们无法编辑,此时就可以使用@Bean注解配置。

@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
    return new JdbcTemplate(dataSource);
}

2.4 @Import

2.4.1 源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
	/**
	 */
	Class<?>[] value();
}

2.4.2 说明

  • 作用:

    该注解是使用在类上的,通常和注解驱动的配置类一起使用,其作用是引入其他配置类。使用此注解之后,可以使我们的注解驱动开发和早期xml配置一样,分别配置不同的内容,使配置更加清晰。同时指定此注解之后,被引入的类上可以不再使用@Configuration,@Component等注解。

  • 属性:

    value:用于指定其他配置类的字节码。它支持指定多个配置类。

  • 使用场景:

    当我们在使用注解驱动开发时,由于配置项过多,如果都写在一个类里面,配置结构和内容将杂乱不堪,此时使用此注解可以把配置项分门别类进行配置。

2.4.3 示例

在入门案例中,我们使用了SpringConfiguration做为主配置类,而连接数据库相关的配置被分配到了JdbcConfig配置类中,此时在SpringConfiguration类上使用@Import注解就可以把JdbcConfig导入进来了。

 /**
 * spring的配置类
 * 用于替代xml配置
 */
@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbc.properties")
@ComponentScan("com.test")
public class SpringConfiguration {
}

/**
 * 连接数据库的配置
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;


    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }


    @Bean("dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

2.5 @PropertySource

2.5.1 源码

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

	/**
	 */
	String name() default "";

	/** 
	 */
	String[] value();

	/** 
	 */
	boolean ignoreResourceNotFound() default false;

	/**
	 */
	String encoding() default "";

	/**
	 */
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

2.5.2 说明

  • 作用:

    用于指定读取资源文件的位置。注意,它不仅支持properties,也支持xml文件,并且通过YAML解析器,配合自定义PropertySourceFactory实现解析yml配置文件。

  • 属性:

    name : 指定资源的名称。如果没有指定,将根据基础资源描述生成。
    value : 指定资源的位置。可以是类路径,也可以是文件路径。
    ignoreResourceNotFound : 指定是否忽略资源文件是否存在,默认false,即当资源文件不存在时spring启动将会报错。
    encoding : 指定解析资源文件使用的字符集。当有中文的时候,需要指定中文的字符集。
    factory : 指定读取对应资源文件的工厂类,默认的是PropertySourceFactory。

  • 使用场景:

    开发中使用注解驱动后,xml配置文件就没有了,此时一些配置如果直接写在类中,会造成和java源码的紧密耦合,修改起来不方便。此时可以使用properties或者yml来配置。

2.5.3 示例

在入门案例中,我们连接数据库的信息如果直接写到JdbcConfig类中,当需要修改时,就面临修改源码的问题,此时使用@PropertySource就可以把配置放到properties文件中了。

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_day01
jdbc.username=root
jdbc.password=1234
/**
 * 连接数据库的配置
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
}

/**
 * spring的配置类
 * 用于替代xml配置
 */
@Configuration
@Import(JdbcConfig.class)
@PropertySource(value = "classpath:jdbc.properties")
@ComponentScan("com.test")
public class SpringConfiguration {
}

3. 注解驱动开发之注入时机和设定注入条件的注解

3.1 @DependsOn

3.1.1 源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {
	String[] value() default {};
}

3.1.2 说明

  • 作用:

    用于指定Bean之间依赖关系的一个注解。spring中没有指定bean的加载顺序,使用此注解则可指定bean的加载顺序。(在基于注解配置中,是按照类中方法的书写顺序决定的)

  • 属性:

    value: 用于指定bean的唯一标识。被指定的bean会在当前bean创建之前加载。

  • 使用场景:

    在观察者模式中,分为事件,事件源和监听器。一般情况下,监听器负责监听事件源,当事件源触发事件之后,监听器就要捕获,并且做出相应的处理。所以我们肯定希望监听器的创建时间在事件源之前,此时就可以使用此注解。

3.1.3 示例

/**
 * @author hao
 */
@Component
public class CustomerListener {

    public CustomerListener(){
        System.out.println("监听器创建了。。。");
    }
}

/**
 * @author hao
 */
@Component
@DependsOn("customerListener")
public class Customer {

    public Customer(){
        System.out.println("事件源创建了。。。");
    }
}

在这里插入图片描述

3.2 @Lazy

3.2.1 源码

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
	boolean value() default true;
}

3.2.2 说明

  • 作用:

    用于指定单例bean对象的创建时机。在没有使用此注解时,单例bean的生命周期与容器相同。但使用此注解之后,单例对象的创建时机变成了第一次使用时创建。注意:这不是延迟加载思想(因为不是每次使用时都创建,只是第一次创建的时机改变了)。

  • 属性:

    value : 指定是否采用延迟加载。默认值为true,表示开启。

  • 使用场景:

    在实际开发中,当我们的Bean是单例对象时,并不是每个都需要一开始都加载到ioc容器之中,有些对象可以在真正使用的时候再加载,当有此需求时,即可使用此注解。值得注意的是,此注解只对单例bean对象起作用,当指定了@Scope注解的prototype取值后,此注解不起作用。

3.2.3 示例

/**
 * @author hao
 */
@Component
@Lazy
//@Lazy(value = false)
//@Scope("prototype")
public class LazyBean {

    public LazyBean(){
        System.out.println("LazyBean对象创建了");
    }
}

/**
 * @author hao
 */
public class SpringAnnotationDrivenTest {

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
      //1.获取容器
      ApplicationContext ac = new AnnotationConfigApplicationContext("config");
      //2.根据id获取对象  
      LazyBean lazyBean = (LazyBean)ac.getBean("lazyBean");
      System.out.println(lazyBean);
    }

}

3.3 @Conditional

3.3.1 源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();
}	

3.3.2 说明

  • 作用:

    它的作用是根据条件选择注入bean对象

  • 属性:

    value : 用于提供一个Condition接口的实现类,实现类中需要编写具体代码实现注入的条件。

  • 使用场景:

    当我们在开发时,可能会使用多平台来测试,例如我们的测试数据库分别部署到了linux和windows两个操作系统上面,现在根据我们的工程运行环境选择连接的数据库。此时就可以使用此注解。同时基于此注解引出的@Profile注解,就是根据不同的环境,加载不同的配置信息。

3.3.3 示例

/**
 * 连接数据库的配置
 * @author hao
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;


   

    /**
     * linux系统注入的数据源
     * @param lDriver
     * @param lUrl
     * @param lUsername
     * @param lPassword
     * @return
     */
    @Bean("dataSource")
    @Conditional(LinuxCondition.class)
    public DataSource createLinuxDataSource(@Value("${linux.driver}") String lDriver,
                                            @Value("${linux.url}")String lUrl,
                                            @Value("${linux.username}")String lUsername,
                                            @Value("${linux.password}")String lPassword){
        DriverManagerDataSource dataSource = new DriverManagerDataSource(lUrl,lUsername,lPassword);
        dataSource.setDriverClassName(lDriver);
        System.out.println(lUrl);
        return dataSource;
    }

    /**
     * windows系统注入的数据源
     * @return
     */
    @Bean("dataSource")
    @Conditional(WindowsCondition.class)
    public DataSource createWindowsDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        System.out.println(url);
        return dataSource;
    }
}

/**
 * @author hao
 */
public class LinuxCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //获取当前环境信息
        Environment environment = context.getEnvironment();
        //获取bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        //获得当前系统名
        String property = environment.getProperty("os.name");
        //包含Windows则说明是windows系统,返回true
        if (property.contains("Linux")){
            return true;
        }
        return false;
    }
}


/**
 * @author hao
 */
public class WindowsCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //获取当前环境信息
        Environment environment = context.getEnvironment();

        /**
         * 获取所有系统环境变量
         */
        if(environment instanceof StandardEnvironment){
            StandardEnvironment standardEnvironment = (StandardEnvironment)environment;
            Map<String,Object> map = standardEnvironment.getSystemProperties();
            for(Map.Entry<String,Object> me : map.entrySet()){
                System.out.println(me.getKey()+","+me.getValue());
            }
        }

        //获取bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        //获得当前系统名
        String property = environment.getProperty("os.name");
        //包含Windows则说明是windows系统,返回true
        if (property.contains("Windows")){
            return true;
        }
        return false;
    }
}
linux.driver=com.mysql.jdbc.Driver
linux.url=jdbc:mysql://localhost:3306/ssm
linux.username=root
linux.password=1234
-------------------------------------------------------------------------------------
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_day01
jdbc.username=root
jdbc.password=1234

4. 用于创建对象的注解

4.1 @Component和三个衍生注解

4.1.1 源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

4.1.2 说明

  • 作用:

    这四个注解都是用于修饰类的,为当前类创建一个对象存入Spring的IOC容器中。在实例化时默认无参构造函数,同时支持带参构造函数,前提是构造函数的参数依赖必须要有值,否则抛异常。

  • 属性:

    value : 用于指定存入容器时bean的id。当不指定时,默认值为当前类的名称。

  • 使用场景:

    当我们需要把自己编写的类注入到IOC容器中,可以使用以上四个注解实现。以上四个注解中@Component注解通常用在非三层对象中,而@Controller,@Service,@Repository三个注解一般是针对三层对象使用的,提供更加精确的语义化配置。

    需要注意的是,Spring在注解驱动开发时,要求必须先接管类对象,然后会处理类中的属性和方法。如果类没有被Spring接管,那么里面的属性和方法上的注解都不会被解析。

4.1.3 示例

  • 正确的方式1:使用默认构造函数。
/**
     * 用于记录系统日志
     * @author hao
     */
    @Component
    public class LogUtil {
        /**
         * 默认无参构造函数
         */
      	public LogUtil(){
		}
      //可以使用aop思想实现系统日志的记录
    }
  • 正确的方式2:在构造函数中注入一个已经在容器中的bean对象。
/**
     * 此处只是举例:使用JdbcTemplate作为持久层中的操作数据库对象
     * @author hao
     */
    @Repository("userDao")
    public class UserDaoImpl implements UserDao{

        private JdbcTemplate jdbcTemplate ;
        /**
         * 此时要求容器中必须有JdbcTemplate对象
         * @param jdbcTemplate
         */
        public UserDaoImpl(JdbcTemplate jdbcTemplate){
            this.jdbcTemplate = jdbcTemplate;
        }
    }
  • 正确的方式3:在构造函数中注入一个在配置文件获取到的值。
/**
     * 用于记录系统日志
     * @author hao
     */
    @Component
    public class LogUtil {
        /**
         * 构造时,注入日志级别
         * @param logLevel
         */
        public LogUtil(@Value("${log.level}")String logLevel){
            System.out.println(logLevel);
        }
      	//可以使用aop思想实现系统日志的记录
    }
  • 错误的方式:由于logLevel没有值,所以运行会报错。
/**
     * 用于记录系统日志
     * @author hao
     */
    @Component
    public class LogUtil {
        /**
         * 构造时,注入日志级别
         * @param logLevel
         */
        public LogUtil(String logLevel){
            System.out.println(logLevel);
        }
      	//可以使用aop思想实现系统日志的记录
    }

5. 用于注入数据的注解

5.1 @Autowired

5.1.1 源码

@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;

}

5.1.2 说明

  • 作用:

    自动按照类型注入。当IOC容器中有且只有一个类型匹配时可以直接注入成功。当有超过一个匹配时,则使用变量名称(写在方法上就是方法名称)作为bean的id,在符合类型的bean中再次匹配,能匹配上就可以注入成功。当匹配不上时,是否报错要看required属性的取值。

  • 属性:

    required:是否必须注入成功。默认值是true,表示必须注入成功。当取值为true的时候,注入不成功会报错。

  • 使用场景:

    此注解的使用场景非常之多,在实际开发中应用广泛。通常情况下我们自己写的类中注入依赖bean对象时,都可以采用此注解。

5.1.3 示例

/**
 * 此处只是举例:使用Jdbc作为持久层中的操作数据库对象
 * @author hao
 */
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao{
	@Autowired
    private JdbcTemplate jdbcTemplate ;
}

5.2 @Qualifier

5.2.1 源码

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {

	String value() default "";

}

5.2.2 说明

  • 作用:

    当使用自动按类型注入时,遇到有多个类型匹配的时候,就可以使用此注解来明确注入哪个bean对象。注意它通常情况下都必须与@Autowired注解一起使用

  • 属性:

    value:用于指定bean的唯一标识。

  • 使用场景:

    在项目开发中,很多时候都会用到消息队列,以ActiveMQ为例。当和spring整合之后,Spring框架提供了一个JmsTemplate对象,它既可以用于发送点对点模型消息也可以发送主题模型消息。如果项目中两种消息模型都用上了,那么针对不同的代码,将会注入不同的JmsTemplate,而容器中出现两个之后,就可以使用此注解注入。当然不用也可以,我们只需要把要注入的变量名称改为和要注入的bean的id一致即可。

5.3 @Resource

5.3.1 源码

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    String lookup() default "";
    Class<?> type() default java.lang.Object.class;
    enum AuthenticationType {
            CONTAINER,
            APPLICATION
    }
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
}

5.3.2 说明

  • 作用:

    作用是找到依赖组件注入到应用来,它利用了JNDI(Java Naming and Directory Interface Java命名目录接口 J2EE规范之一)技术查找所需的资源。如果所有属性都不指定,它默认按照byType的方式装配bean对象。如果指定了name,没有指定type,则采用byName。如果没有指定name,而是指定了type,则按照byType装配bean对象。当byName和byType都指定了,两个都会校验,有任何一个不符合条件就会报错。

  • 属性:

    name:资源的JNDI名称。在spring的注入时,指定bean的唯一标识。
    type:指定bean的类型。
    lookup: 引用指向的资源名称。它可以使用全局JNDI名称链接到任何兼容的资源。
    authenticationType: 指定资源的身份验证类型。它只能为任何受支持类型的连接工厂的资源指定此选项,而不能为其他类型的资源指定此选项。
    shareable:指定此资源是否可以在此组件和其他组件之间共享。
    mappedName:指定资源的映射名称。
    description:指定资源的描述。

  • 使用场景:

    当我们某个类的依赖bean在ioc容器中存在多个的时候,可以使用此注解指定特定的bean对象注入。当然我们也可以使用@Autowired配合@Qualifier注入。

5.4 @Value

5.4.1 源码

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {

	/**
	 * The actual value expression: for example {@code #{systemProperties.myProp}}.
	 */
	String value();

}

5.4.2 说明

  • 作用:

    用于注入基本类型和String类型的数据。它支持spring的EL表达式,可以通过${} 的方式获取配置文件中的数据。配置文件支持properties,xml和yml文件。

  • 属性:

    value : 指定注入的数据或者spring的el表达式。

  • 使用场景:

    在实际开发中,像连接数据库的配置,发送邮件的配置等等,都可以使用配置文件配置起来。此时读取配置文件就可以借助spring的el表达式读取。

5.4.3 示例

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_day01
jdbc.username=root
jdbc.password=1234
/**
 * 连接数据库的配置
 * @author hao
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;


    /**
     * windows系统注入的数据源
     * @return
     */
    @Bean("dataSource")
    public DataSource createWindowsDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

}

5.5 @Inject

5.5.1 源码

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {
}

5.5.2 说明

  • 作用:

    用于建立依赖关系,和@Resource和@Autowired的作用是一样的。

在使用之前需要先导入坐标:
	<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
但是他们之前也有区别:
	@Autowired:来源于spring框架自身。默认是byType自动装配,当配合了@Qualifier注解之后,由@Qualifier实现byName装配。它有一个required属性,用于指定是否必须注入成功。
	@Resource:来源于JSR-250规范。在没有指定name属性时是byType自动装配,当指定了name属性之后,采用byName方式自动装配。
	@Inject:来源于JSR-330规范。它不支持任何属性,但是可以配合@Qualifier或者@Primary注解使用。同时,它默认是采用byType装配,当指定了JSR-330规范中的@Named注解之后,变成byName装配。
  • 属性:无

  • 使用场景:

    在使用@Autowired注解的地方,都可以替换成@Inject。它也可以出现在方法上,构造函数上和字段上,但是需要注意的是:因为JRE无法决定构造方法注入的优先级,所以规范中规定类中只能有一个构造方法带@Inject注解。

5.5.3 示例

/** 
 * 第一种写法: 写在字段上
 * 此处只是举例:使用Jdbc作为持久层中的操作数据库对象
 * @author hao
 */
@Repository("userDao")
public class UserDaoImpl implements UserDao{
	
  	@Inject
    private JdbcTemplate jdbcTemplate ;
  
}

/**
 * 第二种写法:写在构造函数上
 * 此处只是举例:使用Jdbc作为持久层中的操作数据库对象
 * @author hao
 */
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao{
	
    private JdbcTemplate jdbcTemplate ;
    /**
     * 此时要求容器中必须有JdbcTemplate对象
     * @param jdbcTemplate
     */
    @Inject
    public AccountDaoImpl(JdbcTemplate jdbcTemplate){
        this.jdbcTemplate = jdbcTemplate;
    }
}

/**
 * 第三种写法:配合@Named注解使用
 * 此处只是举例:使用Jdbc作为持久层中的操作数据库对象
 * @author hao
 */
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao{
	
  	@Inject
  	@Named("jdbcTemplate")
    private JdbcTemplate jdbcTemplate ;
}

5.6 @Primary

5.6.1 源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {

}

5.6.2 说明

  • 作用:

    用于指定bean的注入优先级,被@Primary修饰的bean对象优先注入。

  • 属性:无

  • 使用场景:

    当依赖对象存在多个时,@Autowired注解已经无法完成功能,此时我们首先想到的是@Qualifier注解指定依赖bean的id。但是此时就产生了,无论有多少个bean,每次都会使用指定的bean注入。但是当我们使用@Primary,表示优先使用被@Primary注解的bean,但是当不存在时还会使用其他的。

5.6.3 示例

/**
 * @author hao
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
//    @Qualifier("accountImpl1")
    private AccountDao accountDao;

    public void save(){
        System.out.println(accountDao);
    }
}


/**
 * 此处只是举例:使用Jdbc作为持久层中的操作数据库对象
 * @author hao
 */
@Repository("accountDaoImpl1")
public class AccountDaoImpl implements AccountDao{

    @Override
    public String toString() {
        return "accountDaoImpl1";
    }
}

/**
 * 此处只是举例:使用Jdbc作为持久层中的操作数据库对象
 * @author hao
 */
@Repository("accountDaoImpl2")
@Primary
public class AccountDaoImpl2 implements AccountDao{

    @Override
    public String toString() {
        return "accountDaoImpl2";
    }
}

6. 和生命周期以及作用范围相关的注解

6.1 @Scope

6.1.1 源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * Alias for {@link #scopeName}.
	 * @see #scopeName
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 * 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 "";

	/**
	 * Specifies whether a component should be configured as a scoped proxy
	 * and if so, whether the proxy should be interface-based or subclass-based.
	 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
	 * @see ScopedProxyMode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

6.1.2 说明

  • 作用:用于指定bean对象的作用范围。

  • 属性:

    value: 指定作用范围的取值。在注解中默认值是""。但是在spring初始化容器时,会借助ConfigurableBeanFactory接口中的类成员:String SCOPE_SINGLETON = "singleton";
    scopeName: 和value的作用是一样的。
    proxyMode: 指定bean对象的代理方式。指定ScopedProxyMode枚举的值 :
    DEFAULT:默认值。(就是NO)
    NO:不使用代理。
    INTERFACES:使用JDK官方的基于接口的代理。
    TARGET_CLASS:使用CGLIB基于目标类的子类创建代理对象。

  • 使用场景:

    在实际开发中,我们的bean对象默认都是单例的。通常情况下,被spring管理的bean都使用单例模式来创建。但是也有例外,例如Struts2框架中的Action,由于有模型驱动和OGNL表达式的原因,就必须配置成多例的。

6.2 @PostConstruct

6.2.1 源码

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}

6.2.2 说明

  • 作用:用于指定bean对象的初始化方法。

  • 属性:无

  • 使用场景:在bean对象创建完成后,需要对bean中的成员进行一些初始化的操作时,就可以使用此注解配置一个初始化方法,完成一些初始化的操作。

6.3 @PreDestroy

6.3.1 源码

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}

6.3.2 说明

  • 作用:用于指定bean对象的销毁方法。

  • 属性:无

  • 使用场景:在bean对象销毁之前,可以进行一些清理操作。

7. @Import注解的使用

7.1 ImportSelector和ImportBeanDefinitionRegistrar介绍

我们在注入bean对象时,可选的方式有很多种。例如:

  • 我们自己写的类,可以使用@Component,@Service,@Repository,@Controller等等。

  • 我们导入第三方库中的类时可以使用@Bean(当需要做一些初始化操作时,比如DataSource),也可以使用@Import注解,直接指定要引入的类的字节码。

但当我们的类很多时,在每个类上加注解会很繁琐,同时使用@Bean或者@Import写起来也很麻烦。此时我们就可以采用自定义ImportSelector或者ImportBeanDefinitionRegistrar来实现。

共同点:他们都是用于动态注册bean对象到容器中的。并且支持大批量的bean导入。

区别:

  • ImportSelector是一个接口,我们在使用时需要自己提供实现类。实现类中返回要注册的bean的全限定类名数组,然后执行ConfigurationClassParser类中的processImports方法注册bean对象。

  • ImportBeanDefinitionRegistrar也是一个接口,需要我们自己编写实现类,在实现类中手动注册bean到容器中。

实现ImportSelector接口或者ImportBeanDefinitionRegistrar接口的类不会被解析成一个Bean注册到容器中。同时,在注册到容器中时bean的唯一标识是全限定类名,而非短类名。

7.2 自定义ImportSelector

/**
 * @author hao
 */
public interface UserService {
    void saveUser();
}

/**
 * @author hao
 */
public class UserServiceImpl implements UserService {

    @Override
    public void saveUser() {
        System.out.println("保存用户");
    }
}

/**
 * @author hao
 */
@Configuration
@ComponentScan("com.test")
@Import(CustomeImportSelector.class)
public class SpringConfiguration {
}
/**
 * customeimport.properties配置文件中的内容:
 * 		custome.importselector.expression= com.test.service.impl.*
 * @author hao
 */
public class CustomeImportSelector implements ImportSelector {

    private String expression;

    public CustomeImportSelector(){
        try {
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("customeimport.properties");
            expression = loadAllProperties.getProperty("custome.importselector.expression");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 生成要导入的bean全限定类名数组
     * @param importingClassMetadata
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.创建存放全限定类名的集合
        Set<String> classes = new HashSet<>();
        //12.填充集合数据
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        //13.按照规则返回
        return classes.toArray(new String[classes.size()]);
    }
}

/**
 * 测试类
 * @author hao
 */
public class SpringCustomeImportSelectorTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("config");
        String[] names = ac.getBeanDefinitionNames();
        for(String beanName : names){
            Object obj = ac.getBean(beanName);
            System.out.println(beanName+"============"+obj);
        }
    }
}

7.3 自定义ImportBeanDefinitionRegistrar

借助7.2小节的案例代码,只需要把配置改一下:

/**
 * @author hao
 */
@Configuration
@ComponentScan("com.test")
@Import(CustomeImportDefinitionRegistrar.class)
public class SpringConfiguration {
}

/**
 * 自定义bean导入注册器
 * @author hao
 */
public class CustomeImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    private String expression;

    public CustomeImportDefinitionRegistrar(){
        try {
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("customeimport.properties");
            expression = loadAllProperties.getProperty("custome.importselector.expression");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.扫描指定包
        scanner.scan(basePackages);
    }
}

8. @Profile注解的使用

8.1 使用场景分析

@Profile注解是spring提供的一个用来标明当前运行环境的注解。我们正常开发过程中经常遇到的问题是,开发环境是一套环境,测试是一套环境,线上部署又是一套环境。这样从开发到测试再到部署,会对程序中的配置修改多次,尤其是从测试到上线这个环节,测试人员也不敢保证改了哪个配置之后还能不能在线上运行。为了解决上面的问题,我们一般会使用一种方法,就是针对不同的环境进行不同的配置,从而在不同的场景中跑我们的程序。而spring中的@Profile注解的作用就体现在这里。在spring使用DI来注入的时候,能够根据当前制定的运行环境来注入相应的bean。最常见的就是使用不同的DataSource了。

8.2 代码实现

  • 自定义不同环境的数据源
package config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;

/**
 * @author hao
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;




    /**
     * @return
     */
    @Bean("dataSource")
    @Profile("dev")
    public DruidDataSource createDevDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaxActive(10);
        return dataSource;
    }

    /**
     * @return
     */
    @Bean("dataSource")
    @Profile("test")
    public DruidDataSource createTestDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaxActive(50);
        return dataSource;
    }

    /**
     * @return
     */
    @Bean("dataSource")
    @Profile("produce")
    public DruidDataSource createProduceDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaxActive(100);
        return dataSource;
    }
}
  • 编写配置类
/**
 * @author hao
 */
@Configuration
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
  • 编写测试类
/**
 * @author hao
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
@ActiveProfiles("test")
public class SpringProfileTest {

    @Autowired
    private DruidDataSource druidDataSource;

    @Test
    public void testProfile(){
        System.out.println(druidDataSource.getMaxActive());
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/572806.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 使用相对路径读取文件失败

python open一个问及那时使用绝对路径可以&#xff0c;但是使用相对路径时报错&#xff0c;找不到指定文件 解决步骤如下&#xff1a; 添加Python配置 在新增的配置Json文件添加下图红框这一行

阿里云OSS

进入阿里云官网&#xff0c;手机号短信登录

Ansible 中的copy 复制模块应用详解

作者主页&#xff1a;点击&#xff01; Ansible专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月25日13点40分 Ansible 中的 copy 模块用于将文件或目录从本地计算机或远程主机复制到远程主机上的特定位置。它是一个功能强大的模块&#xff0c;可用于各种文…

prometheus helm install 如何配置告警模版

对接企业微信 获取企业id 注册完成之后&#xff0c;通过企业微信官网登录后台管理&#xff0c;在【我的企业】的企业信息里面&#xff0c;获取到Alertmanager服务配置需用到的第一个配置&#xff1a;企业ID 获取部门id 部门ID 在【通讯录】中&#xff0c;添加一个子部门&a…

无人机+自组网:2U机架车载式自组网电台技术详解

自组网的特点包括自发现、自动配置、自组织和自愈等。由于网络中的节点可以随时加入或离开&#xff0c;自组网需要能够自动感知拓扑结构的变化&#xff0c;并快速调整路由策略以适应新的网络环境。此外&#xff0c;自组网中的节点还需要具备节能、安全和分布式管理等特性&#…

maixcam如何无脑运行运行别人的模型(以安全帽模型为例)

maixcam如何无脑运行运行别人的模型&#xff08;以安全帽模型为例&#xff09; 本文章主要讲如何部署上传的模型文件&#xff0c;以及如果你要把你模型按照该流程应该怎么修改&#xff0c;你可以通过该文章得到你想要的应该&#xff0c;该应用也包含的退出按钮&#xff0c;是屏…

质量管理系统( QMS):一文扫盲,质量重于泰山。

一、什么是QMS系统 QMS系统是质量管理系统&#xff08;Quality Management System&#xff09;的缩写。它是一种组织内部用于管理和控制质量相关活动的体系&#xff0c;旨在确保产品或服务符合质量标准和客户要求。 QMS系统通常包括一系列文件、程序和流程&#xff0c;用于规…

Linux常用命令总结(四):文件权限及相关命令介绍

1. 文件属性信息解读 1. 文件类型和权限的表示 0首位表示类型。在Linux中第一个字符代表这个文件是目录、文件或链接文件 符号对应文件类型-代表文件dd 代表目录l链接文档(link file)&#xff1b; 1-3位确定属主&#xff08;该文件的所有者&#xff09;拥有该文件的权限。 4-6…

【信息收集】端口扫描masscan负载均衡识别lbd

★★免责声明★★ 文章中涉及的程序(方法)可能带有攻击性&#xff0c;仅供安全研究与学习之用&#xff0c;读者将信息做其他用途&#xff0c;由Ta承担全部法律及连带责任&#xff0c;文章作者不承担任何法律及连带责任。 1、什么是masscan masscan在kali系统上是自带的端口扫描…

【golang学习之旅】报错:a declared but not used

目录 报错原因解决方法参考 报错 代码很简单&#xff0c;如下所示。可以发现a和b都飙红了&#xff1a; 运行后就会出现报错&#xff1a; 报错翻译过来就是a已经声明但未使用。当时我很疑惑&#xff0c;在其他语言中从来没有这种情况。况且这里的b不是赋值了吗&#xff0c;怎…

SSL证书一年要多少钱

经常会被人问道&#xff1a;“安装SSL证书一年得要多少钱&#xff1f;”其实证书的类型不同、功能不同、品牌不同&#xff0c;证书的价格也会不一样。SSL证书的价格因其类型、品牌、购买渠道以及所包含的服务和支持等因素而有所不同。通常一年的费用从几十元到几万元不等。 一…

[Swift]单元测试

编写单元测试是确保你的代码质量和功能正确性的重要步骤 一、编写单元测试的详细流程 1. 创建一个新的Xcode项目 如果你尚未创建一个项目&#xff0c;首先你需要在Xcode中创建一个新的iOS项目&#xff1a; 打开Xcode&#xff0c;选择“File” > “New” > “Project”…

【黑马点评Redis——004达人探店】

1.发布探店笔记 2.点赞 利用Redis中的Set集合来判断是否点赞过。 3.点赞排行榜 可以通过SortedSet来按点赞时间进行排序。 4.好友关注 4.1.关注和取关 4.2.共同关注 可以通过set实现交集的功能 4.3.关注推送 4.3.1 拉模式 拉模式&#xff08;Pull&#xff09;&#x…

Android 13 Layer数据结构

Layer::State state的定义 State mDrawingState; 一个mDrawingState的变量struct State {Geometry active_legacy;Geometry requested_legacy;int32_t z;ui::LayerStack layerStack;#endifuint32_t flags;uint8_t reserved[2];int32_t sequence; // changes when visible regi…

解决Oracle锁表的方法

在实际工作中&#xff0c;并发量比较大的项目&#xff0c;经常会出现锁表的问题&#xff0c;下面我将复现这个问题&#xff0c;并给出解决方法。 一、问题复现 1、session1修改aabb表的B字段为迪迦奥特曼&#xff0c;但是不提交该事务。 2、session2也修改这行的这个字段。 发…

基于Springboot的网上商城购物系统

基于SpringbootVue的网上商城购物系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 商品信息 商品资讯 后台登录页面 后台管理首页 用户管理 商品分类管…

SVG 绘制微信订阅号icon

效果 代码 <!DOCTYPE html> <html> <body><svg xmlns"http://www.w3.org/2000/svg" version"1.1" width"600" height"600"><rect x"0" y"0" rx"0" ry"0" width&…

【C++】5.C语言/C++内存管理

一、C/C内存分布 栈中存储的是局部变量&#xff0c;函数参数&#xff0c;返回值等 堆主要用于动态内存分配 数据段用以存储全局数据和静态数据 代码段存储可执行代码和常量 二、C语言和C中的内存管理方式 在C语言中&#xff0c;我们使用 malloc、calloc、realloc、free来进…

AI-数学-高中-43常见函数的导数

原作者视频&#xff1a;【导数】【一数辞典】2常见函数的导数_哔哩哔哩_bilibili

【C++】——类和对象(构造函数,析构函数,拷贝构造函数,赋值运算符重载)

创作不易&#xff0c;多多支持&#xff01; 前言 相信你对这几个知识点有点混淆&#xff0c;相信看完以后&#xff0c;你会对此有一个清晰的认识。 一 类的6个默认成员函数 如果我们写一个类&#xff0c;但是类里面什么都没有&#xff0c;我们称之为空类。 其实这个类也不…
最新文章