spring之注解使用

@Configuration

基本概念

@Configuration和@Bean

Spring新的配置体系中最为重要的构件是:@Configuration标注的类,@Bean标注的方法。

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

    @AliasFor(annotation = Component.class)
    String value() default "";
    // @since 5.2
    boolean proxyBeanMethods() default true;
    
}

用@Configuration注解标注的类表明其主要目的是作为bean定义的源。此外,@Configuration类允许通过调用同一类中的其他@Bean method方法来定义bean之间的依赖关系(下有详解)。

// @since 3.0
@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;
    // @since 5.1
    boolean autowireCandidate() default true;
    String initMethod() default "";
    String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
    
}

@Bean注解标注在方法上,用于指示方法实例化、配置和初始化要由Spring IoC容器管理的新对象。对于熟悉Spring的 XML配置的人来说,@Bean注解的作用与元素相同。您可以对任何Spring的@Component组件使用@Bean注释的方法代替(注意:这是理论上,实际上比如使用@Controller标注的组件就不能直接使用它代替)。

需要注意的是,通常来说,我们均会把@Bean标注的方法写在@Configuration标注的类里面来配合使用。

简单粗暴理解:@Configuration标注的类等同于一个xml文件,@Bean标注的方法等同于xml文件里的一个 标签

使用举例

@Configuration
public class AppConfig {

    @Bean
    public User user(){
        User user = new User();
        user.setName("A哥");
        user.setAge(18);
        return user;
    }

}
public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        User user = context.getBean(User.class);
        System.out.println(user.getClass());
        System.out.println(user);
    }
}

输出:

class com.yourbatman.fullliteconfig.User
User{name='A哥', age=18}

Full模式和Lite模式

Full模式和Lite模式均是针对于Spring配置类而言的,和xml配置文件无关。值得注意的是:判断是Full模式 or Lite模式的前提是,首先你得是个容器组件。至于一个实例是如何“晋升”成为容器组件的,可以用注解也可以没有注解,本文就不展开讨论了,这属于Spring的基础知识。

Lite模式

当@Bean方法在没有使用@Configuration注释的类中声明时,它们被称为在Lite模式下处理。它包括:在@Component中声明的@Bean方法,甚至只是在一个非常普通的类中声明的Bean方法,都被认为是Lite版的配置类。@Bean方法是一种通用的工厂方法(factory-method)机制。

和Full模式的@Configuration不同,Lite模式的@Bean方法不能声明Bean之间的依赖关系。因此,这样的@Bean方法不应该调用其他@Bean方法。每个这样的方法实际上只是一个特定Bean引用的工厂方法(factory-method),没有任何特殊的运行时语义。

何时为Lite模式

官方定义为:在没有标注@Configuration的类里面有@Bean方法就称为Lite模式的配置。透过源码再看这个定义是不完全正确的,而应该是有如下case均认为是Lite模式的配置类:

  • 类上标注有@Component注解
  • 类上标注有@ComponentScan注解
  • 类上标注有@Import注解
  • 类上标注有@ImportResource注解
  • 若类上没有任何注解,但类内存在@Bean方法

以上case的前提均是类上没有被标注@Configuration,在Spring 5.2之后新增了一种case也算作Lite模式:

  • 标注有@Configuration(proxyBeanMethods =
    false),注意:此值默认是true哦,需要显示改为false才算是Lite模式

细心的你会发现,自Spring5.2(对应Spring Boot 2.2.0)开始,内置的几乎所有的@Configuration配置类都被修改为了@Configuration(proxyBeanMethods = false),目的何为?答:以此来降低启动时间,为Cloud Native继续做准备。

优缺点
优点:
  1. 运行时不再需要给对应类生成CGLIB子类,提高了运行性能,降低了启动时间
  2. 可以该配置类当作一个普通类使用喽:也就是说@Bean方法可以是private、可以是final
缺点:
  1. 不能声明@Bean之间的依赖,也就是说不能通过方法调用来依赖其它Bean
  2. (其实这个缺点还好,很容易用其它方式“弥补”,比如:把依赖Bean放进方法入参里即可)
代码示例

主配置类:

@ComponentScan("com.yourbatman.fullliteconfig.liteconfig")
@Configuration
public class AppConfig {
}

准备一个Lite模式的配置:

@Component
// @Configuration(proxyBeanMethods = false) // 这样也是Lite模式
public class LiteConfig {

    @Bean
    public User user() {
        User user = new User();
        user.setName("A哥-lite");
        user.setAge(18);
        return user;
    }


    @Bean
    private final User user2() {
        User user = new User();
        user.setName("A哥-lite2");
        user.setAge(18);

        // 模拟依赖于user实例  看看是否是同一实例
        System.out.println(System.identityHashCode(user()));
        System.out.println(System.identityHashCode(user()));

        return user;
    }

    public static class InnerConfig {

        @Bean
        // private final User userInner() { // 只在lite模式下才好使
        public User userInner() {
            User user = new User();
            user.setName("A哥-lite-inner");
            user.setAge(18);
            return user;
        }
    }
}

测试用例:

public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 配置类情况
        System.out.println(context.getBean(LiteConfig.class).getClass());
        System.out.println(context.getBean(LiteConfig.InnerConfig.class).getClass());

        String[] beanNames = context.getBeanNamesForType(User.class);
        for (String beanName : beanNames) {
            User user = context.getBean(beanName, User.class);
            System.out.println("beanName:" + beanName);
            System.out.println(user.getClass());
            System.out.println(user);
            System.out.println("------------------------");
        }
    }
}

结果输出:

1100767002
313540687
class com.yourbatman.fullliteconfig.liteconfig.LiteConfig
class com.yourbatman.fullliteconfig.liteconfig.LiteConfig$InnerConfig
beanName:userInner
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite-inner', age=18}
------------------------
beanName:user
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite', age=18}
------------------------
beanName:user2
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite2', age=18}
------------------------

小总结

  • 该模式下,配置类本身不会被CGLIB增强,放进IoC容器内的就是本尊
  • 该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
  • 该模式下,配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新实例而并非IoC容器内的单例
  • 该模式下,配置类就是一普通类嘛,所以@Bean方法可以使用private/final等进行修饰(static自然也是阔仪的)

Full模式

在常见的场景中,@Bean方法都会在标注有@Configuration的类中声明,以确保总是使用“Full模式”,这么一来,交叉方法引用会被重定向到容器的生命周期管理,所以就可以更方便的管理Bean依赖。

何时为Full模式

标注有@Configuration注解的类被称为full模式的配置类。自Spring5.2后这句话改为下面这样我觉得更为精确些:

标注有@Configuration或者@Configuration(proxyBeanMethods = true)的类被称为Full模式的配置类
(当然喽,proxyBeanMethods属性的默认值是true,所以一般需要Full模式我们只需要标个注解即可)

优缺点
优点:
  1. 可以支持通过常规Java调用相同类的@Bean方法而保证是容器内的Bean,这有效规避了在“Lite模式”下操作时难以跟踪的细微错误。特别对于萌新程序员,这个特点很有意义
缺点:
  1. 运行时会给该类生成一个CGLIB子类放进容器,有一定的性能、时间开销(这个开销在SpringBoot这种拥有大量配置类的情况下是不容忽视的,这也是为何Spring 5.2新增了proxyBeanMethods属性的最直接原因)
  2. 正因为被代理了,所以@Bean方法 不可以是private、不可以是final
代码示例

主配置:

@ComponentScan("com.yourbatman.fullliteconfig.fullconfig")
@Configuration
public class AppConfig {
}

准备一个Full模式的配置:

@Configuration
public class FullConfig {

    @Bean
    public User user() {
        User user = new User();
        user.setName("A哥-lite");
        user.setAge(18);
        return user;
    }


    @Bean
    protected User user2() {
        User user = new User();
        user.setName("A哥-lite2");
        user.setAge(18);

        // 模拟依赖于user实例  看看是否是同一实例
        System.out.println(System.identityHashCode(user()));
        System.out.println(System.identityHashCode(user()));

        return user;
    }

    public static class InnerConfig {

        @Bean
        // private final User userInner() { // 只在lite模式下才好使
        public User userInner() {
            User user = new User();
            user.setName("A哥-lite-inner");
            user.setAge(18);
            return user;
        }
    }
}

测试用例:

public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 配置类情况
        System.out.println(context.getBean(FullConfig.class).getClass());
        System.out.println(context.getBean(FullConfig.InnerConfig.class).getClass());

        String[] beanNames = context.getBeanNamesForType(User.class);
        for (String beanName : beanNames) {
            User user = context.getBean(beanName, User.class);
            System.out.println("beanName:" + beanName);
            System.out.println(user.getClass());
            System.out.println(user);
            System.out.println("------------------------");
        }
    }
}
结果输出:

550668305
550668305
class com.yourbatman.fullliteconfig.fullconfig.FullConfig$$EnhancerBySpringCGLIB$$70a94a63
class com.yourbatman.fullliteconfig.fullconfig.FullConfig$InnerConfig
beanName:userInner
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite-inner', age=18}
------------------------
beanName:user
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite', age=18}
------------------------
beanName:user2
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite2', age=18}
------------------------

小总结

  1. 该模式下,配置类会被CGLIB增强(生成代理对象),放进IoC容器内的是代理
  2. 该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
  3. 该模式下,配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向IoC内的那个单例
  4. 该模式下,@Bean方法不能被private/final等进行修饰(很简单,因为方法需要被复写嘛,所以不能私有和final。defualt/protected/public都可以哦),否则启动报错(其实IDEA编译器在编译器就提示可以提示你了)


在这里插入图片描述

Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'user2' must not be private or final; change the method's modifiers to continue
Offending resource: class path resource [com/yourbatman/fullliteconfig/fullconfig/FullConfig.class]
    at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
    at org.springframework.context.annotation.BeanMethod.validate(BeanMethod.java:50)
    at org.springframework.context.annotation.ConfigurationClass.validate(ConfigurationClass.java:220)
    at org.springframework.context.annotation.ConfigurationClassParser.validate(ConfigurationClassParser.java:211)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:326)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:242)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532)
    at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
   

@Bean

基础用法
定义:@Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。

用法:标识这个方法可以产生一个Bean并且交给Spring容器管理,告诉Spring可以在这个方法中拿到一个Bean。

PS:添加的bean的id为方法名

@Configuration
public class AppConfig {

    @Bean//用@Bean注解配置Bean时,bean的ID默认为方法的名称
    public TransferService transferService() {
        return new TransferServiceImpl();
    }

}

上面的代码等同于

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

bean的依赖
@bean 也可以依赖其他任意数量的bean,如果TransferService 依赖 AccountRepository,我们可以通过方法参数实现这个依赖。

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {//PS:此方法的入参就是返回值所依赖的对象
        return new TransferServiceImpl(accountRepository);
    }

}

@Bean注解修饰带参数方法时,参数取值

在这里插入图片描述
会从Spring容器中根据类型注入(若有多个类型的的话则根据方法名按名称注入,没有找到就会报错)

@Bean注解配置一个bean,方法的形式参数是另外一个bean的时(依赖注入)

直接上代码:

/**
     * 直接模式队列1
     */
    @Bean
    public Queue directOneQueue() {
        return new Queue(RabbitConsts.DIRECT_MODE_QUEUE_ONE);
    }
 
    /**
     * 队列2
     */
    @Bean
    public Queue queueTwo() {
        return new Queue(RabbitConsts.QUEUE_TWO);
    }
 
    /**
     * 队列3
     */
    @Bean
    public Queue queueThree() {
        return new Queue(RabbitConsts.QUEUE_THREE);
    }
 
    /**
     * 分列模式队列
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(RabbitConsts.FANOUT_MODE_QUEUE);
    }
 
    /**
     * 分列模式绑定队列1
     *
     * @param directOneQueue 绑定队列1
     * @param fanoutExchange 分列模式交换器
     */
    @Bean
    public Binding fanoutBinding1(Queue directOneQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(directOneQueue).to(fanoutExchange);
    }
 
    /**
     * 分列模式绑定队列2
     *
     * @param queueTwo       绑定队列2
     * @param fanoutExchange 分列模式交换器
     */
    @Bean
    public Binding fanoutBinding2(Queue queueTwo, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queueTwo).to(fanoutExchange);
    }
 
    /**
     * 主题模式队列
     * <li>路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email</li>
     * <li>通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了</li>
     * <li>通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配</li>
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(RabbitConsts.TOPIC_MODE_QUEUE);
    }
 
 
    /**
     * 主题模式绑定分列模式
     *
     * @param fanoutExchange 分列模式交换器
     * @param topicExchange  主题模式交换器
     */
    @Bean
    public Binding topicBinding1(FanoutExchange fanoutExchange, TopicExchange topicExchange) {
        return BindingBuilder.bind(fanoutExchange).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_ONE);
    }
 
    /**
     * 主题模式绑定队列2
     *
     * @param queueTwo      队列2
     * @param topicExchange 主题模式交换器
     */
    @Bean
    public Binding topicBinding2(Queue queueTwo, TopicExchange topicExchange) {
        return BindingBuilder.bind(queueTwo).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_TWO);
    }
 
    /**
     * 主题模式绑定队列3
     *
     * @param queueThree    队列3
     * @param topicExchange 主题模式交换器
     */
    @Bean
    public Binding topicBinding3(Queue queueThree, TopicExchange topicExchange) {
        return BindingBuilder.bind(queueThree).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_THREE);
    }

解释说明:这是一个@Configration的配置类里面配置的是自定义的bean.

  • 首先配置了三个无参数的Queue类型的bean:
    directOneQueue, queueTwo, queueThree
  • 在其他的bean的方法中用到了该Queue的bean(依赖传递,依赖注入)
    如fanoutBinding2注入了queueTwo,此时方法的形式参数并不是只是一个变量,spring先根据Queue默认去找此类型的bean,发现存在上述的三个bean,接着就按照指定的bean的名字queueTwo查找,找到一个bean,就是用queueTwo这个bean,做为依赖传递给fanoutBinding2
  • 如果根据方法的形式参数找不到bean,那么就会更据参数类型去查找,结果找到三个,此时spring会报错,当然配置多个同名的bean同样会报错

@Autowired

先讲 一个@Autowired注解的例子

先给出src的包:很基础的 三层dao、service、servlet
在这里插入图片描述
BookDao.java:就一个saveBook()方法

package com.atshiyou.dao;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

@Repository

public class BookDao {
	
	public void saveBook() {
		System.out.println("保存了一本图书");
	}
}

BookService.java :里面就一个save()方法

package com.atshiyou.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.atshiyou.dao.BookDao;

@Service
public class BookService {
	
	@Autowired
	private BookDao bookDao;
	
	public void save() {
		System.out.println("<bookService>..正在调用dao帮你保存图书");
		bookDao.saveBook();
	}
}

BookServlet.java,doGet方法里调用save方法

package com.atshiyou.servlet;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.atshiyou.service.BookService;

@Controller
public class BookServlet {
	
	//自动装配,自动地为这个属性赋值
	@Autowired
	private BookService bookService;
	
	public void doGet() {
		bookService.save();
	}
}

Test文件中:调用doGet方法查看输出

package com.atshiyou.test;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.atshiyou.service.BookService;
import com.atshiyou.servlet.BookServlet;

class IocTest {

	ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
	
	@Test
	public void test2() {
		BookServlet bean = ioc.getBean(BookServlet.class);
		bean.doGet();		
	}
}

结果:
在这里插入图片描述

@Autowired原理

@Autowired原理:
		举例:@Autowired
			 private BookService bookService;
		1)、先按照类型去容器中找到对应的组件;bookService = ioc.getBean(BookService.class)
			①、找到一个:找到就赋值
			②、没找到就报异常
			③、按照类型可以找到多个?找到多个如何装配上?
			    a、类型一样就按照变量名为ID继续匹配
			      Ⅰ、匹配上就装配
			      Ⅱ、没有匹配?报错
			      		原因:因为我们按照变量名作为id继续匹配的
			      		使用@Qualifier指定一个新的id
			      			找到就匹配

对于a、这种情况,按照变量名作为id继续匹配,还是匹配不到的话,可以使用新的注解:
@Qualifier:指定一个新id作为id,让spring不使用变量名作为id

如果@Qualifier也找不到就会报错

总结┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳┳
用例子总结:
@Autowired
private BookService bookService;
总结:autowired 为组件自动赋值,自动查找ioc容器,找到就赋值,查找的方式是从类型寻找,有可能找到多个,比如说有一个继承于要查找的类,如果是多个就按照变量名作为id继续匹配
举例说:查找到多个相同的类是BookService 和 BookServiceExtend,这 个 时候会根据你要查找的bookservice 来查找id,BookService 的id默认是bookservice,BookServiceExtend的id默认是bookServiceExtend,一查找就只匹配上BookService 类了

如果现在把例子该了:改成bookService2了

@Autowired
private BookService bookService2;

Spring容器先查找BookService类,一找就找到两个类(BookService 和BookServiceExtend),然后查找id,发现都不匹配,会直接报错
这个时候诞生了@Qualifier注解,让spring不使用bookService2作为id去查找,而是使用一个比如说bookService去查找,作为新的id去查找

@Qualifier"bookService"@Autowired
private BookService bookService2;

@Qualifier注解找不到就报错,找到就装配

所以发现Autowired标注的自动装配的属性默认是一定装配上的,找到就装配,找不到就拉倒
可以在Autowired(request = false)默认是true,找不到就会报错,置为false后,找不到会置为null

@Qualifier"bookService"@Autowired(request=falseprivate BookService bookService2;

@Autowired和@Resource区别

  • @Autowired注解可以实现自动装配,只要在对应的属性上标记该注解,但是@Autowired注解只按照byType注入。
public class UserController {

    @Autowired
    private IUserService userService;
}
  • @Resource注解可以实现自动装配,它有两个重要属性name和type,name属性解析为bean的名字,type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
  • @Autowired注解和@Resource注解的作用相同,只不过@Autowired按照byType注入,如果@Autowired想使用名称可以结合@Qualifier注解进行使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值