基于注解配置概述
Spring的注解配置是替代XML配置的一个替代方法,依赖注解(基于字节码和元数据)来描述元信息。
你可以理解为把之前那个XML的形式改成Java注解的方式来替代,对应的注解以及属性来描述这个Bean的配置元素,再根据这个配置元素去进行初始化和注入依赖。(配置信息从XML到组件类中)
以下有几个问题,关于XML配置文件和注解形式:
(1)那这个基于注解的方式是不是就是一定比XML配置文件强?
不一定,XML你可以不接触源码就来配置,但是大家觉得POJO的形式,这个配置信息是源码的一部分,所以后面很多人觉得应该把这个配置信息移到源码中,这样注解就比较好。
但是注解的问题就是太分散了,这个文件有一个,那个文件有一个,太分散了。你不能很容易找齐注解,和配置文件一个文件集中到一起的相比确实不方便。
(2)如果XML和注解同时存在会怎么样?如果同一个Bean,注解一个,XML配置文件一个,这样这个Bean会怎么样?
实际上二者可以是兼容的,但是同一个Bean两个方式都配置了,会出现什么问题,实际上不会不错。注解注入的方式会在XML注解注入的方式,先注入。但是由于是先注入注解的,但是XML在后面注入会覆盖。
Spring2.5还增加了对JSR-250注解的支持,如@PostConstruct和@PreDstroy。
Spring 3则为javax.inject包中包含的JSR-330(Java依赖注入)注解添加了支持,例如@Inject和@Named。
XML开启注解支持
开启注解支持的快捷方式就是在原始的XML配置文件中加入context Schema约束。
XMl开启注解 添加contrext Schema
原始的Spring XML配置文件,加入context Schema约束
加入约束前的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
加入约束后的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
Spring XML文件是Spring框架早期版本中的核心配置文件,主要用于定义和管理应用程序中的组件、依赖关系及框架功能。以下是其核心作用及与注解配置的关系。
(当然后面可以用别的注解,来替代这个context schema来开启注解。)
XML开启注解:开启注解支持
前面只是通过Spring XML打开注解,你还要处理开启检测,处理某些注解的功能。
(实际上,我们现在开的注解的方式是,纯Java配置,也就是说@configuration+@ComponentScan这种纯Java配置)
annotation-config
这个注解就是为了已经在容器注册的bean,进行注解检测激活,对于bean的注册则不检测。
所以需要XML或者其他形式,先把这个bean放入到容器中,annotation-config才会对这些bean相关内部注解,进行激活控制。
这个配置是帮助我们注册4个BeanPostProcessor的bean,主要这4个bean是为:
AutowireAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,
RequiredAnnotationBeanPostProcessor,
PersistenceAnnotationBeanPostProcessor这4个bean,这4个不同的BeanPostProcessor对应处理检测不同类型的注解(相当于每一个BeanPostProcessor对应一个类型的注解,相当于注解处理器。)
常见的context:annotetion-config配置,可激活的注解如下:
(1)Spring的@Required,@Autowired,@Lookup,@Value(@Required 检查依赖 setter方法,@Autowire 依赖注入,@Lookup 动态获取原型bean)
(2)JSR-330@inject(“如果可用”,指的是支持版本,spring3.0版本开始,他对标Spring原生的注解是@Autowired)
(3)JSR-250 @Resource,@PostConstruct和@PreDestor
@Lookup 用于解决单例 Bean 依赖原型 Bean 时的作用域冲突问题,每次调用返回新实例(就是一个原型的bean但是他需要一个单例的bean)
(4)JAW-WS 的 @WebServiceRef
@Component
@Scope("prototype")
public class PrototypeBean { /* 原型 Bean */ }
@Component
public class SingletonBean {
@Lookup
public PrototypeBean getPrototypeBean() {
return null; // 实际由代理实现
}
}
Spring 在运行时生成代理类,覆盖被 @Lookup 标记的方法。
每次调用该方法时,代理会从容器中获取新的原型 Bean 实例
方法需为 abstract 或返回 null(实际逻辑由代理实现)。
访问权限不能为 private(需可被覆盖)
(1)@PostConstruct
标记一个bean在完成在依赖完成注入,和bean初始化阶段完成,执行你定义的逻辑。和Spring的initializingBean的接口功能类似,
(2)@Autowire,@inject和@Reosurce
这三个都可以依赖注入
按类型注入
// 三者等效(假设只有一个 UserService 实现类)
@Autowired private UserService userService; // Spring
@Inject private UserService userService; // JSR-330
@Resource private UserService userService; // JSR-250
按名称注入
// @Autowired + @Qualifier
@Autowired
@Qualifier("adminUserService")
private UserService userService;
// @Inject + @Named
@Inject
@Named("adminUserService")
private UserService userService;
// @Resource 直接指定 name
@Resource(name = "adminUserService")
private UserService userService;
构造器注入
// Spring @Autowired (4.3+ 可省略)
@Autowired
public OrderService(UserService userService) { ... }
// @Inject 必须显式标注
@Inject
public OrderService(UserService userService) { ... }
// @Resource 不支持构造器注入!
// 错误示例:@Resource public OrderService(UserService userService) { ... }
常见的注解的使用
@Component组件
@Component注解标注在类上,IoC容器启动时,就会在Component-scan范围内扫描标注@Component注解的类,将他们注册在IoC容器中。我们称之为组件,就是相当于xml文件< bean >的标签,就是定义一个bean,把它交给容器。
自Spring2.5起,web分层结构项目中,为了区分组件式负责哪一个部分那一层。根据@Component组件衍生出3个组件注解,@Respository @Service和@Controller,默认是单例。
@Controller注解表示的是表现层注解,@Service表示的是服务层的注解,@Respository表示的是持久层组件。组件不好归类的时候,才会用这个@Component组件。
IoC注解配置第一例
新建一个空的maven工程,引入依赖
<properties>
<spring-framework.version>5.2.8.RELEASE</spring-framework.version>
</properties>
<dependencies>
<!--spring 核心组件所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
添加spring-config.xml配置文件,开启注解支持:
在这里插入代码片
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.spring.core.*"/>
</beans>
bean的命名
扫描到一个被托管的组件,它对应的bean名称由BeanNameGenerator策略和@Component,@Respository,@service和@Controller注解指定的value值一起生成。
注解设置了value值,value值是作为bean的名字,外部类的bean的name不能重复。(以下这个就是在@Component组件的value值设置,设置的是componentTwo)。
普通内部类之间是可以value值重复,启动时不会报错,但是不建议,可能会造成bean覆盖,运行时报异常。静态内部类之间bean name 不能重复。因此,指定的bean name都不应该重复。
@Component("componentTwo")
public class ComponentTwo {
public ComponentTwo() {
System.out.println("ComponentTwo:" + this);
}
}
没有设置value的bean name生成的策略:
1 对于外部类,默认的bean name的生成器返回非限定类名,如果类名有多个字符并且前两个字符是大写的,将直接返回简单类名。
2普通内部类,默认bean名称生成器返回全限定类名$内部类名。
3对于静态内部类,默认bean名称生成器将返回小写的非限定外部类名.内部类名。静态内部类之间bean name 不能重复。
自定义bean name生成器
不想依赖设置value和默认的bean命名策略,我们可以自定义一个bean name生成器。
/**
* bean名称生成器
*
* @author lx
*/
public class MyNameGenerator implements BeanNameGenerator {
/**
* bean的命名接口
*
* @param definition 用于生成名称的bean的定义
* @param registry bean定义注册中心
* @return 自定义的bean name
*/
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
//简单的直接返回bean的类名,仅供测试
return definition.getBeanClassName();
}
}
在xml生成
<context:component-scan base-package="com.spring.core.*" name-generator="com.spring.core.ann.MyNameGenerator"/>
不建议自定义bean name 生成器。
3.2 @Bean工厂
在Spring的一系列Component组件注解或者@Configuration配置注解的类的内部,可以使用@Bean注解的bean的name,表示向IoC容器添加bean定义。
换个说法就是,向IoC容器中添加bean的两种方式:
(1)@Component组件注解
(2)@Configuration注解配置在类内部中添加@Bean注解
@Bean注解作用类似于<.bean >标签,和这个@Component注解区别就是,相当于使用工厂实例化bean。使用@Bean注册的bean,默认命名策略是取方法作为bean的名字。
@Bean一般用于注册用@Bean的方式了。实际上@Bean注解更多的使用在@Configuration配置注解类内部。
以下是一个样例,一个是@Component组件,在组件里有一个@Bean注解
/**
* @author lx
*/
@Component
public class BeanFactoryDemo {
// /**
// * 由于方法名为beanFactoryDemo,和@Component的命名策略冲突导致名字重复,进而抛出异常
// */
// @Bean
// public BeanFactoryDemo beanFactoryDemo() {
// return new BeanFactoryDemo();
// }
/**
* 使用@Bean的方式注册bean
*/
@Bean
public BeanFactoryDemo getBeanFactoryDemo() {
return new BeanFactoryDemo();
}
}
测试代码:
@Test
public void beanFactory() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
//这是@Component注册的bean
System.out.println(ac.getBean("beanFactoryDemo", BeanFactoryDemo.class));
//这是@Bean注册的bean
System.out.println(ac.getBean("getBeanFactoryDemo", BeanFactoryDemo.class));
}
返回的结果是两个bean都实例化。
3.3 @Scope组件作用域
基于组件注解的默认作用域是单例的,想不指定作用域,可以使用@Scope注解设置作用域。
@Scope注解是在两种情况下起作用。一个是在@Component组件注解的类,另一个是@Bean注解的标注的工厂方法。类似xml配置文件的< bean >标签的scope属性。
只会配置singleton和prototype两种作用域,甚至基本不会配置,默认就是singleton。
3.3.1生命周期管理
在XML配置文件上,生命周期更长的bean中注入生命周期更短的bean时,可以使用查找方法注入< lookup-method/>来解决短生命周期的bean实例的获取的问题,使得短生命周期的bean能够被正常管理。
同样Spring注解也有这样的设置,@Scope注解也能解决这个问题。主要的就是设置它的proxyMode属性,使得注入的是一个代理对象,生命周期更长的bean获取生命周期更短的bean实例时,实际上是通过代理对象去获取具有正常生命周期的bean实例的。