Spring 常用注解说明

这篇文章主要用于说明在日常开发中经常使用到的注解。

1. @Configuration

 该注解用于类上,表明这是一个配置类。在Spring MVC中,项目的配置通常放在 xml文件中, 为此需要先写一个配置文件,例如在xml中使用bean标签来注册一些组件。如下:

    <bean id="person" class="com.zhao.springboot.bean.Person">
        <property name="age" value="22"></property>
        <property name="name" value="zhangsan"></property>
    </bean>

在注解式开发中 就用该注解 来替代 xml配置文件,在配置文件中注册组件,就被替换成在配置类中添加组件。如下所示:

// 配置类==配置文件
@Configuration //告诉SpringBoot 这是一个配置类
public class MainConfig {
    @Bean  // 给容器中注入一个Bean ,类型为返回值的类型,id默认是用方法名作为id
    public Person person() {
        return new Person("lisi", 25);
    }
}

测试代码如下:

ApplicationContext annoContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = annoContext.getBean(Person.class);
        System.out.println(person);

输出结果如下:

注意: @Bean 向容器中注入Bean ,id默认为方法的名称,可以通过name改变 ,@Bean(name="person1")

 2. @ComponentScan

该注解用于指定包扫描,用于类上。 Spring MVC中通常用 <context:component-scan /> 来指定扫描范围,只要标注了@Controller、@Service、@Repository、@Component就会被扫描到。注解式开发中使用 @ComponentScan 来替代。

 在刚才的配置类上使用该注解进行标注:  value指定扫描的位置

测试如下:

 分别建立 controller、service、dao类。并用相关注解进行标识。

@Repository
public class BookDao {
}
.....
@Service
public class BookService {
}
.....
@Controller
public class BookController {
}

编写测试代码:

@Test
    public void test01() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }

输出如下:

 说明: 配置类(MainConfig)自身也会被注册到bean容器中(@Configuration中包含了一个@Component注解) ,此外@ComponentScan注解中还包含有其他可以用的功能,如扫描时只包含哪些(includeFilters)、只排除哪些(excludeFilters)。 

2.1 excludeFilters 

 eg :  排除 所有Controller和Service

@ComponentScan(value = "com.zhao.springboot",excludeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})

测试结果:

2.2  includeFilters 

eg: 只包含 Controller和Service 

 

@ComponentScan(value = "com.zhao.springboot",includeFilters = {
        //type 指定根据什么规则排除
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
},useDefaultFilters = false)

 测试结果:

注意: 使用 includeFilters 时需要禁用掉默认的过滤规则,即需要设置useDefaultFilters =false(默认为true) 

2.3 @ComponentScans

@ComponentScan 还是一个重复注解(@Repeatable(ComponentScans.class)),在java8中,可以多次使用该注解来指定不同的扫描策略:

假如不是java8 ,也可以通过 @ComponentScans来达到想要的效果。

2.4  FilterType :

 @Filter中的type指定 根据某种规则过滤,

有以下几种规则: 

public enum FilterType {
    ANNOTATION,  // 按照注解
    ASSIGNABLE_TYPE, //按照给定的类型
    ASPECTJ, // 使用ASPECTJ表达式,不是很常用
    REGEX,  // 使用正则表达式
    CUSTOM; // 自定义规则

    private FilterType() {
    }
}

 上面已经使用了 按照注解  FilterType.ANNOTATION 类型, 这里重点说明 FilterType.ASSIGNABLE_TYPE 和FilterType.CUSTOM。

2.4.1  给定类型 FilterType.ASSIGNABLE_TYPE:使用如下

@ComponentScan(value = "com.zhao.springboot",includeFilters = {
        //type 指定根据什么规则排除
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
        @Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class})
},useDefaultFilters = false)

测试结果:

2.4.2  自定义 类型

自定义过滤类型,需要实现TypeFilter (org.springframework.core.type.filter.TypeFilter)

用法如下:

public class MyTypeFilter implements TypeFilter {
    /**
     * @param metadataReader        读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取当前类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 获取当前正在扫描的类的信息 ,类型,接口是什么......
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        String superClassName = classMetadata.getSuperClassName();
        System.out.println("当前扫描类的类名---> " +className);
       // System.out.println("当前扫描类的父类---> " +superClassName);
        if (className.contains("er")) { // 类名中包含er,就包含进去
            return true;
        }
        return false;
    }
}

测试结果:

3. @Scope

在IOC容器中,我们在IOC容器中添加的实例默认都是单实例的 。也可以通过 @Scope 来指定它的作用范围。

@Scope 取值有 以下几种:

 

 

prototype 多实例
singleton 单实例 (默认)
request :同一次请求创建一个实例
session: 同一个session创建一个请求

 后两种并不常用,所以这里仅仅说明前两种。

3.1. singleton 

默认为单实例的(@Scope("singleton")),测试如下:

@Bean("person")
@Scope
public Person person() {
    System.out.println("给容器中添加Person");
    return new Person("张三", 25);
}
    @Test
    public void test02() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器创建完成......");

        Object bean1 =context.getBean("person");
        Object bean2 =context.getBean("person");
        System.out.println(bean1 ==bean2);

    }

运行结果如下:

 另外,从测试结果还能看出 在单实例情况下,当IOC容器创建时,同时也会调用方法创建对象并放到IOC容器中,以后每次获取,就是直接从容器中拿。

 3.1.1 懒加载 @Lazy

 由于单实例bean默认在容器启动时创建组件实例,可以设置懒加载,使容器启动时不创建实例,而在第一次使用(获取)实例时创建,并初始化,注意:懒加载仅仅适用于单实例。

测试如下:

@Configuration
public class MainConfig3 {
    @Bean("person")
    @Lazy
    public Person person() {
        System.out.println("给容器中添加Person");
        return new Person("张三", 25);
    }
}
   @Test
    public void test03() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        System.out.println("IOC容器创建完成......");

//        Object bean1 =context.getBean("person");
//        Object bean2 =context.getBean("person");
//        System.out.println(bean1 ==bean2);

    }

 测试结果:

 在测试如下:

 @Test
    public void test03() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        System.out.println("IOC容器创建完成......");

        Object bean1 =context.getBean("person");
        Object bean2 =context.getBean("person");
        System.out.println(bean1 ==bean2);

    }

 测试结果如下:

3.2  prototype

当@Scope的值设置为 prototype 时,则添加的组件在容器中是多实例的,IOC容器创建时,并不会调用方法创建该组件的实例,而是每当使用到该组件时,就去创建一个实例。

 测试如下: 

    @Bean("person")
    @Scope("prototype")
    public Person person() {
        System.out.println("给容器中添加Person");
        return new Person("张三", 25);
    }
 @Test
    public void test02() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
//        System.out.println("IOC容器创建完成......");
//
//        Object bean1 =context.getBean("person");
//        Object bean2 =context.getBean("person");
//        System.out.println(bean1 ==bean2);

    }

执行测试方法,会发现控制台并没有输出  创建Person的实例的语句(给容器中添加Person)。

在进行测试如下:

@Test
    public void test02() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器创建完成......");

        Object bean1 =context.getBean("person");
        Object bean2 =context.getBean("person");
        System.out.println(bean1 ==bean2);
    }

输出如下:

 4. @Conditional

 该条件用于 按照一定的条件进行判断,满足条件进行判断,满足条件给容器中注册bean

其值是一个 Condition数组, Condition是一个包含一个方法的接口:

boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

context : 判断条件能使用到的上下文(环境) ,  metadata 注释信息。

使用如下:

@Configuration
public class MainConfig4 {
  
    @Bean("Windows")
    public Person person01() {
        return new Person("Windows", 50);
    }

    @Bean("Linux")
    public Person person02() {
        return new Person("Linux", 40);
    }
}

假如在配置类中向容器中注入两个Person实例,分别为 Windows和Linux ,想要实现根据操作系统(Windows或Linux)不同而分别注入不同的实例。那么,可以这样做:

首先编写两个类实现Condition类,并实现  matches 方法。WindowsCondition和 LinuxCondition(与WindowsCondition代码基本一致,不再重复贴)

public class WindowsCondition implements Condition {
   
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 1. 能获取ioc 使用的beanFactory ,装配的工厂。
        ConfigurableListableBeanFactory factory = context.getBeanFactory();
        // 2. 获取类加载器
        ClassLoader loader = context.getClassLoader();
        //3. 获取当前环境信息,包括环境变量、虚拟机变量等
        Environment environment = context.getEnvironment();
        //4. 获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        String operationName = environment.getProperty("os.name");
//        System.out.println(operationName);
        if (operationName.contains("Windows")) {
            return true;
        }
        return false;
    }

然后在配置类中使用 @Conditional 注解引入。

   @Conditional({WindowsCondition.class})
    @Bean("Windows")
    public Person person01() {
        return new Person("Windows", 50);
    }

    @Conditional({LinuxCondition.class})
    @Bean("Linux")
    public Person person02() {
        return new Person("Linux", 40);
    }

编写测试类如下:

 @Test
    public void test04() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
        System.out.println("IOC容器创建完成......");
        Environment environment = context.getEnvironment();
        String operationName =environment.getProperty("os.name");
        System.out.println("操作系统名称:"+operationName);

        Map<String, Person> beans = context.getBeansOfType(Person.class);
        System.out.println(beans);
    }

输出结果如下:

另外 @Conditional 也可以标注在类上 ,那么此时的含义为满足当前条件,该类中配置的所有bean注册才会生效。

@Configuration
@Conditional({LinuxCondition.class})
public class MainConfig2 {
    @Bean("person")
    public Person person() {
        System.out.println("给容器中添加Person");
        return new Person("张三", 25);
    }
}

 编写测试方法:

@Test
    public void test04() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器创建完成......");
        Environment environment = context.getEnvironment();
        String operationName =environment.getProperty("os.name");
        System.out.println("操作系统名称:"+operationName);

        Map<String, Person> beans = context.getBeansOfType(Person.class);
        System.out.println(beans);
    }

测试结果:

 5. 向容器中注册组件相关注解

给容器注册组件除了 上面的

   包扫描+组件标注注解(@Controller /@Service...) ,

   @Bean[导入的第三方包里面的组件]

也可以通过 @Import 快速给容器中导入一个组件 ,下面说明@Import用法。

5.1 @Import (要导入到容器中的组件)

  该方式会自动向容器中注册组件,id默认为全类名

@Configuration
public class MainConfig5 {
}
....
public class Color {
}
.....
public class Red {
}

编写测试代码:

private void printBeans(ApplicationContext context) {
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name:beanDefinitionNames) {
            System.out.println(name);
        }
    }

@Test
public void test05(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
        printBeans(context);
   }

输出结果如下:

由于此时 配置类中并没有注册 Red 和Color ,所以并没有打印出其名称。修改配置类MainConfig5 ,加上@Import注解:

@Configuration
@Import({Color.class, Red.class})
public class MainConfig5 {
}

测试结果:

5.2   ImportSelector

  ImportSelector 导入选择器, 是一个接口,里面包含一个  selectImports 方法, 通过实现该方法能实现返回 需要导入类的全类名,进而向容器注册组件。SpringBoot中该方式用的也比较多。

 用法: 编写类实现 ImportSelector接口,实现里面的 selectImports:

public class MyImportSelector implements ImportSelector {

    // 返回值,就是导入到容器中的组件全类名
    // AnnotationMetadata : 当前标注 @Import注解的类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        return new String[]{"com.zhao.springboot.bean.Blue","com.zhao.springboot.bean.Yellow"};
        //方法不要返回null
    }
}

测试:

@Configuration
@Import({Color.class, Red.class, MyImportSelector.class})
public class MainConfig5 {
}

测试结果如下:

注意:MyImportSelector 并不会被注册到容器中,该类实现了 ImportSelector,仅仅是把方法返回值中的全类名注册到容器中。

5.3  ImportBeanDefinitionRegistrar

 也可以手动注册组件,通过实现  ImportBeanDefinitionRegistrar中的 registerBeanDefinitions 方法来注册 ,用法如下:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     *
     * @param importingClassMetadata :当前类的注解信息
     * @param registry : BeanDefinition注册类,把所有需要添加到容器中的bean
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean redDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Blue");
        boolean blueDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Yellow");

        if (redDefinition && blueDefinition){
            // 指定bean定义信息,(Bean 类型..等)
            BeanDefinition definition = new RootBeanDefinition(RainBow.class);
//            definition.setScope();
            // 指定bean名称
              // String beanName, BeanDefinition beanDefinition
             // 注册一个bean
            registry.registerBeanDefinition("rainBow",definition);
        }
    }
}

测试如下:

 5.4 使用Spring 提供的FactoryBean (工厂bean)注册

使用方式如下:

public class ColorFactoryBean  implements FactoryBean<Color> {

    // 返回一个Color对象,这个对象将会被添加到容器中
    @Override
    public Color getObject() throws Exception {
        System.out.println("ColorFactoryBean........getObject....");
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }
    /*
       是否是单例? true:单例,在容器中保存一份,
                   false: 多实例, 每次获取都会创建一个新的bean
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

 将 ColorFactoryBean 注册到容器中:

 @Bean
    public FactoryBean colorFactoryBean() {
        return new ColorFactoryBean();
    }

测试:

   @Test
    public void test06(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);

        // 工厂bean 获取的是 调用 getObject创建的对象
        Object colorFactoryBean = context.getBean("colorFactoryBean");
        Object colorFactoryBean1 = context.getBean("colorFactoryBean");
        System.out.println(colorFactoryBean.getClass().getName());
        System.out.println(colorFactoryBean == colorFactoryBean1);
        // 如果想获取 工厂bean本身 ,可以使用 &
        Object colorFactoryBean3 = context.getBean("&colorFactoryBean");
        System.out.println(colorFactoryBean3.getClass().getName());
    }

测试结果:

 6. Bean生命周期相关注解

 容器管理bean的生命周期,我们可以自定义初始化和销毁方法,容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法。

1) 指定初始化和销毁方法;

通过@Bean指定init-method和destory-method 。具体用法如下:

public class Car {
    public Car() {
        System.out.println("Car...constructor.....");
    }

    public void init(){
        System.out.println("Car.....init.....");
    }

    public void destroy() {
        System.out.println("Car......destroy....");
    }
}
.....
@Configuration
public class MainConfigOfLifeCycle {

    @Bean(initMethod ="init",destroyMethod ="destroy")
    public Car car() {
        return new Car();
    }
}

编写测试方法:

注意: 单实例的销毁发生在容器关闭的时候,而当设置为多实例时,容器不会管理这个bean,容器不会调用销毁方法。

 

   @Bean(initMethod ="init",destroyMethod ="destroy")
    @Scope("prototype")
    public Car car() {
        return new Car();
    }
......
@Test
    public void test01() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完成.......");
        context.getBean("car");
        context.close();
    }

测试结果:

2) 通过让Bean 实现 InitializingBean(定义初始化逻辑), DisposableBean(定义销毁逻辑)

public class Cat implements InitializingBean, DisposableBean {
    public Cat() {System.out.println("Cat...constructor.....");}
    @Override
    public void destroy() {
        System.out.println("Cat......destroy....");
    }
    @Override
    public void afterPropertiesSet() {
        System.out.println("Cat.....afterPropertiesSet.....");
    }
}
......
@Bean
public Cat cat(){
   return  new Cat();
}

测试结果:

 3) 可以使用 JSR250 

@PostConstruct :在bean创建完成并属性赋值完成,来执行初始化方法。
@PreDestroy:在容器销毁bean之前,通知我们进行销毁工作。

使用如下:

public class Dog {
    public Dog() {
        System.out.println("Dog...constructor");
    }
    @PreDestroy
    public void destroy() {
        System.out.println("Dog......@PreDestroy....");
    }
    @PostConstruct
    public void init() {
        System.out.println("Dog.....@PostConstruct.....");
    }
}
.....
 @Bean
 public Dog dog() {
    return new Dog();
 }

测试结果如下:

4) 使用 BeanPostProcessor【Interface】 

 bean的后置处理器。在bean初始化前后进行一些处理工作。即在afterPropertiesSet 或init-method 前后进行一系列的操作。

 有以下两个api :

// 在初始化之前工作
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;    
//在初始化之后工作
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;  

使用如下:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         // bean : the new bean instance
        System.out.println("postProcessBeforeInitialization...." + beanName+">= " +bean);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization......" + beanName+">= " +bean);
        return bean;
    }
}

注意:要将实现类加入到bean容器。测试结果如下:

debug查看该执行流程,发现   执行初始化方法的时候(invokeInitMethods)  ,先调用 applyBeanPostProcessorsBeforeInitialization,执行完初始化方法后又执行了 applyBeanPostProcessorsAfterInitialization:

执行流程: 

doCreateBean -->  createBeanInstance--> initializeBean--> applyBeanPostProcessorsBeforeInitialization -->invokeInitMethods--> applyBeanPostProcessorsAfterInitialization ...(AbstractAutowireCapableBeanFactory)

7. @Value

@Value 为属性赋值 ,  支持 : 1) 基本数值  2) 可以写 SpEL ;#{ }  3) 可以写${} ;取出配置文件的值(在环境变量里面的值)

使用如下:

public class Student {

    @Value("李四")
    private  String name;
    @Value("#{25-5}")
    private Integer age;

   // ... 省略 getter 、setter、constructor 、toString
}

打印出该容器中所有bean ,结果如下:

若需要使用 ${}取出配置文件中的值,需要使用到另一个注解: @PropertySource ,该注解用于指定配置文件的位置,从而可以使用${} 取出配置文件中的值。下面说明用法:

给Student类中添加一个nickName的字段, 此时由于没有赋值,重新测试,打印出容器中的bean ,显示该字段为null:

写一个配置文件(Student.properties):

student.nickName=小灰灰

在配置类上进行修改:

@Configuration
@PropertySource(value={"classpath:/student.properties"}) // 指定配置文件的位置
public class MainConfigOfPropertyValues {
    @Bean
    public Student student() {
        return new Student();
    }
}

测试:

ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("student.nickName");

8. 自动装配的相关注解

 8.1  @Autowired :自动注入

用法如下:

@Service
public class BookService {
    @Autowired
    private BookDao bookDao;
// toString 省略
}
.....
@Repository
public class BookDao {
    private String label = "1";
   // getter、setter、toString省略
}
.....
@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {
}

 测试代码:

  @Test
    public void test01() {
        BookService bookService = context.getBean(BookService.class);
        System.out.println(bookService);  
    }

 测试结果:

注意:

(1) 默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
(2)如果找到了多个相同类型的组件,在将属性的名称作为组件的id去容器中查找
             applicationContext.getBean("bookDao");
(3) 使用 @Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名
(4) 自动装配默认一定要将属性赋值好,没有就会报错,也可以使用 @Autowired(required =false),设置不必须装配
(5) @Primary:让spring 进行自动装配的时候,默认使用首选的bean,也可以继续使用@Qualifier指定需要装配的bean的名字

测试如下:

 1)容器中存在两个bean

@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {

    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao dao = new BookDao();
        dao.setLabel("2");
        return dao;
    }
}

运行测试方法进行测试:

2) 使用@Primary指定默认装配的bean

    @Primary
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao dao = new BookDao();
        dao.setLabel("2");
        return dao;
    }

 测试结果:

3)@Qualifier指定 要装配的组件的id 

  在 2)的基础上进入修改:  

@Service
public class BookService {
    @Autowired
    @Qualifier("bookDao")
    private BookDao bookDao;
// toString 省略
}

测试结果:

8.2  Spring 还支持使用 @Resource (JSR250) 和@Inject(JSR330)[JAVA规范的注解]

 @Resource可以和 @Autowired 一样实现自动装配功能,默认是安装组件名称进行装配的。但是没有能支持@Primary,也没有支持@Autowired(required=false)的功能。其使用如下:

@Service
public class BookService {
   // @Autowired
   // @Qualifier("bookDao")
    @Resource(name ="bookDao2")
    private BookDao bookDao;
// toString 省略
}

测试结果:

如果想要使用@Inject, 还需要引入一个依赖:

<dependency>
     <groupId>javax.inject</groupId>
     <artifactId>javax.inject</artifactId>
     <version>1</version>
</dependency>

 使用如下:

@Service
public class BookService {
   // @Autowired
   // @Qualifier("bookDao")
   // @Resource(name ="bookDao2")
    @Inject
    private BookDao bookDao;
// toString 省略
}

测试如下:

引入了 配置类中使用@Bean注入的实例,是因为 该实例使用了@Primary注解,也即 @Inject 和@Autowired功能一样,但是@Inject没有 @Autowired的required=false的功能。

8.3 @Autowired 标注的位置

@Autowired 可以标注在:  构造器、参数、方法、 属性  ,这些都是从容器中获取组件参数的值。

1) 标注在方法位置:用的最多的就是 @Bean  +方法参数,参数从容器中自动获取,默认不写@Autowired
2) 标注在构造器上: 如果组件只有一个有参构造器,那么这个有参构造器的@AutoWired可以省略,参数位置的组件还是可以从容器中自动获取
3) 标注在方法中的参数上
@Component
public class Boss {
//    @Autowired
    private Car car;
//    @Autowired  // 可以直接标注在方法上
    public void setCar(Car car) {
        this.car = car;
    }
   // 省略 getter 和 toString方法
   // @Autowired  // 可以标注在构造器上
  //public Boss(@Autowired Car car) { // 如果组件只有一个有参构造器,那么这个有参构造器的@Autowired可以省略,参数位置的组件还是可以从容器中自动获取
    public Boss( Car car){
        this.car = car;
    }
}
  @Bean
//  public Boss boss(@Autowired Car car) {  //Car只有一个有参构造器,所以可以省略 @Autowired
    public Boss boss( Car car) {
        return new Boss(car);
    }

8.4  自定义组件想要使用Spring 容器底层的一些组件(ApplicationContext,BeanFactory,xxxx) 

如果想要使用Spring容器底层组件,只需要让自定义组件 实现xxxAware即可。在创建对象的时候,会调用接口规定的方法注入相关组件。顶层 接口为 Aware(org.springframework.beans.factory)

使用如下:

public class Cup implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    @Override
    public void setBeanName(String name) {
        System.out.println("当前bean的名称: " + name);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("传入的IOC 容器: " + applicationContext);
        this.applicationContext = applicationContext;
    }
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) { //解析字符串
        String resolveStringValue = resolver.resolveStringValue("你好,${os.name} 我是 #{20*18}");
        System.out.println("解析的字符串:" + resolveStringValue);
    }
}
.......
   @Bean
    public Cup cup() {  // 向容器中注入 bean
        return new Cup();
    }

编写测试代码:

    @Test
    public void test02(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAutowired2.class);
        System.out.println(context);
    }

测试结果如下:

9. @Profile

@Profile 是Spring为我们提供的,具有可以根据当前环境,动态的激活和切换一系列组件的功能。该注解指定组件在哪个环境的情况下才能被注册到容器中,若不指定,任何环境下都能注册这个组件。

 1) 加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,默认是default环境

 2) 标注在 配置类上,只有在指定的环境的时候,整个配置类里面的所有配置才能开始生效

使用如下:(以多数据源为例。引入c3p0依赖)

@Configuration
@PropertySource("classpath:/dbConfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
    @Value("${db.user}")
    private String user;
    private String driverClass;
    private StringValueResolver resolver;

      @Profile("default")
//    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("db.password") String password) throws Exception {
        return getDataSource(password,"jdbc:mysql://localhost:3306/test");
    }
    
    //@Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("db.password") String password) throws Exception {
        return getDataSource(password,"jdbc:mysql://localhost:3306/dev");
    }
    @Profile("prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("db.password") String password) throws Exception {
        return getDataSource( password ,"jdbc:mysql://localhost:3306/prod");
    }
    private DataSource getDataSource(String password, String url) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl(url);
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.resolver = resolver;
        this.driverClass = resolver.resolveStringValue("${db.driverClass}");
    }
}

该配置类写的有点复杂,也是为了练习前面的注解 ,测试代码如下:

   @Test
    public void test01() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
        String[] names = context.getBeanNamesForType(DataSource.class);
        for (String name : names) {
            System.out.println(name);
        }
   }

测试结果如下:

激活prod环境测试如下:

AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext();
        context.getEnvironment().setActiveProfiles("prod"); //允许设置多个活动的环境
        context.register(MainConfigOfProfile.class);
        context.refresh();
        String[] names = context.getBeanNamesForType(DataSource.class);
        for (String name : names) {
            System.out.println(name);
        }
.....

测试如下:

 注意:出现devDataSource 数据源是因为,配置类上该数据源并没有指定环境,所以默认任何环境下都注册了。

也可以通过 指定虚拟机参数的方法激活环境  , 加上 -Dspring.profiles.active=prod

 10. 切面相关注解

前置通知(@Before) :在目标方法执行前执行
后置通知(@After): 目标方法运行结束后执行(无论正常还是异常结束)
返回通知(@AfterReturning) : 在目标方法正常返回后执行
异常通知(@AfterThrowing): 在目标方法出现异常后执行
环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
@Aspect :告诉spring这是一个切面类
@EnableAspectJAutoProxy :开启基于注解的aop模式

使用方法步骤如下:

 假如有需求如下: 做除法运算,并返回结果。 并且 在业务逻辑执行的时候将日志进行打印(方法运行前、运行结束、出现异常)

1)首先定义一个业务类(默认已经导入了aop相关依赖):

public class MathCalculator {
    public int div(int i, int j) {
        System.out.println("MathCalculator..div...");
        return i / j;
    }
}

2)定义日志切面

@Aspect//指明这个一个切面类
public class LogAspects {

    // @Before 在目标方法之前切入:切入点表达式(指定在哪个方法切入)
//    @Before("com.zhao.springboot.aop.MathCalculator.div(int ,int )")
//    @Before("com.zhao.springboot.aop.MathCalculator.*(..)")
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) { // 注意:当方法有多个参数时,JoinPoint必须放置第一位置
        Object[] args = joinPoint.getArgs();
        System.out.println(joinPoint.getSignature().getName() + " 除法运行...参数列表: {" + Arrays.asList(args) + "}");
    }
    @After("com.zhao.springboot.aop.LogAspects.pointCut()")
    public void logEnd(JoinPoint joinPoint) {
        System.out.println(joinPoint.getSignature().getName() + " 除法结束");
    }
    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(Object result) {
        System.out.println("除法正常返回。。。运行结果: {" + result + "}");
    }
    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void logException(Exception ex) {
        System.out.println("除法异常。。。异常信息: {" + ex + "}");
    }
    // 抽取公共的切入点表达式
    // 1. 本类引用  pointCut()
    //2. 其他的切面引用 com.zhao.springboot.aop.LogAspects.pointCut()
    @Pointcut("execution(public  int com.zhao.springboot.aop.MathCalculator.*(..))")
    public void pointCut() {
    }
}

3) 将切面类和业务逻辑类加入到容器中,并开启基于注解的aop模式

@EnableAspectJAutoProxy // 开启注解
@Configuration
public class MainConfigOfAop {
    @Bean
    public MathCalculator mathCalculator() {
        return new MathCalculator();
    }
    @Bean
    public LogAspects logAspects() {
        return new LogAspects();
    }
}

测试代码:

   @Test
    public void test01() {
        // 1. 不要自己创建对象 ,需要使用spring创建的实例,才能使用到spring的切面
//        MathCalculator calculator =new MathCalculator();
//        calculator.div(1,1);

        MathCalculator calculator = context.getBean(MathCalculator.class);
//        calculator.div(1, 1);
        calculator.div(1, 1);
    }

测试结果如下:

 注意:切面类上必须加上 @Aspect 表名该类为一个切面类,并且 配置类上也需要使用 @EnableAspectJAutoProxy

开启基于注解的aop模式,此时切面才会生效。 除外次,要使用Spring容器创建的bean实例,才会触发切面。

11  与事务相关

@Transactional  //标注在方法上,表示当前方法是一个事务方法
@EnableTransactionManagement  // 开启事务管理功能

使用步骤如下:

1. 配置数据源、JdbcTemplate(Spring 提供的简化数据库操作的工具)操作数据
2. 给方法上标注@Transactional 表示当前方法是一个事务方法
3. 开启事务管理功能。
4. 往容器中 配置一个事务管理器,并管理 dataSource

 代码使用如下:

@Configuration
@PropertySource("classpath:/dbConfig.properties")
@ComponentScan("com.zhao.springboot.config.tx")
@EnableTransactionManagement   //开启事务管理功能
public class TxConfig {
    @Value("${db.user}")
    private String user;
    @Value("${db.password}")
    private String password;
    @Value("${db.driverClass}")
    private String driverClass;

    @Bean
    public DataSource comboPooledDataSource() throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
    // 配置JdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception { 
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(comboPooledDataSource());
        return jdbcTemplate;
    }
    // 注册事务管理器
    @Bean  // PlatformTransactionManager 为事务管理器的顶层接口
    public PlatformTransactionManager transactionManager() throws Exception {
        return new DataSourceTransactionManager(comboPooledDataSource());
    }
}
 ....  dao层
@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void insert() {
        String sql = "INSERT INTO `tbl_user` ( `username`, `age`) VALUES (?, ?);";
        String username = UUID.randomUUID().toString().substring(0, 5);
        jdbcTemplate.update(sql, username, 19);
    }
}
.... service层
@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    @Transactional   // 标注 这个一个事务方法
    public void addUser(){
        userDao.insert();
        System.out.println("插入成功");
        int i=1/0;
    }
}

测试代码:

 @Test
    public void test01() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.addUser();
    }

测试前数据库数据:

 测试后:

数据库数据:

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值