@Component
@Service
@Controller
@Repository
a> 引入依赖
spring-aop-5.1.5.RELEASE.jar
b> 开启组件扫描
最简单的开启注解
<context:component-scan base-package="com.jianan" />
默认是4个注解都可以创建对象,但是我们也可以自定义,可以指定哪些组件,也可以排除哪些组件
可以详细设置一些内容
<context:component-scan base-package="com.jianan" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
c> 在类上面添加注解创建注解
@Configuration
// 开启注解扫描
@ComponentScan(basePackages = {"com.atguigu"})
public class SpringConfig {
}
上面介绍到,@ComponentScan
用于开始注解扫描,那么看一下该注解的源码:
1.@ComponentScan value指定要扫描的包
2.excludeFilters = Filter[] 指定扫描排除的组件
3.includeFilters = Filter[] 指定包含哪些组件
4.Filter 默认扫描注解类型,也可以自定义
5.当指定扫描组件的时候需要禁用默认扫描组件 useDefaultFilters = false
6.@ComponentScan注解类上有@Repeatable注解,代表是可重复注解
7.我们也可以通过ComponentScans[]来些多个注解扫描方式
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)// 这个的 @Repeatable代表是一个重复注解,即可以重复使用
public @interface ComponentScan {
@AliasFor("value")
String[] basePackages() default {};
// 是否使用默认的4个注解
boolean useDefaultFilters() default true;
// 指定包含哪些组件
ComponentScan.Filter[] includeFilters() default {};
// 指定扫描排除的组件
ComponentScan.Filter[] excludeFilters() default {};
// 省略一些代码...
}
使用示例
@Configuration
// @ComponentScan value指定要扫描的包
// excludeFilters = Filter[] 指定扫描排除的组件
@ComponentScan(value = "com.jianan", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
// includeFilters = Filter[] 指定包含哪些组件
// 当指定扫描组件的时候需要禁用默认的扫描组件 useDefaultFilters = false
@ComponentScan(value = "com.jianan", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
},useDefaultFilters = false)
public class MainConfig {
}
FilterType
在@ComponentScan
,我们可以指定过滤的类型,按照注解、类等扫描,也可以按照自定义的扫描
public enum FilterType {
ANNOTATION,// 按照注解扫描
ASSIGNABLE_TYPE,// 按照给定的类型扫描
ASPECTJ, // 使用ASPECTJ表达式
REGEX, // 使用正则指定
CUSTOM; // 使用自定义表达式
private FilterType() {
}
}
自定义过滤类型
1)创建类实现TypeFilter接口
public class MyTypeFilter implements TypeFilter {
/**
*
* @param metadataReader 读取到当前正在扫描的类的信息
* @param metadataReaderFactory 可以获取到其它任何类的信息
* @return
* @throws IOException
*/
@Override
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();
System.out.println("----->" + className);
return false;
}
}
2)配置类设置
@Configuration
@ComponentScan(value = "com.jianan", includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
},useDefaultFilters = false)
public class MainConfig {
}
3)测试
public static void main(String[] args) {
// 通过注解配置类
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
// 获取Person类在IOC容器中名字
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);//person1
}
}
上面可以注解 + 注解扫描
来创建对象,那么其创建的都是通过调用无参构造方法,那么我们想在创建的时候,同时注入属性,可以通过
@Configuration //作为配置类,替代 xml 配置文件
@ComponentScan(basePackages = {"com.atguigu"})
public class SpringConfig {
@Bean
public User user(){
User user = new User();
// 可以在这里设置对象属性
user.setId(1);
return user;
}
}
该注解存在2个属性:
value()属性很好理解,对应@Component注解的value
proxyBeanMethods() 从注释上看到是从5.2开始推出的,意思就是代理Bean的方法,当属性值不同时,代表两种模式
Full(proxyBeanMethods = true)、全量模式【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
Lite(proxyBeanMethods = false)、增量模式【每个@Bean方法被调用多少次返回的组件都是新创建的】
组件依赖必须使用Full模式。也就是在一个配置类里面,一个组件的注册依赖了另外一个类,此时必须保证组件是单实例,也就是在获取的时候需要先检查是否已经创建了
其他默认是Lite模式,这样容器在启动的时候就没有必要检测是否已经存在该实例,可以加快容器启动速度
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 这里有@Component 代表配置类里面的@Bean会注册组件,同时配置类本身也是组件
public @interface Configuration {
// 该注解代表将value值映射到@Component
@AliasFor(
annotation = Component.class
)
String value() default "";
/**
* Specify whether {@code @Bean} methods should get proxied in order to enforce
* bean lifecycle behavior, e.g. to return shared singleton bean instances even
* in case of direct {@code @Bean} method calls in user code. This feature
* requires method interception, implemented through a runtime-generated CGLIB
* subclass which comes with limitations such as the configuration class and
* its methods not being allowed to declare {@code final}.
* The default is {@code true}, allowing for 'inter-bean references' via direct
* method calls within the configuration class as well as for external calls to
* this configuration's {@code @Bean} methods, e.g. from another configuration class.
* If this is not needed since each of this particular configuration's {@code @Bean}
* methods is self-contained and designed as a plain factory method for container use,
* switch this flag to {@code false} in order to avoid CGLIB subclass processing.
*
Turning off bean method interception effectively processes {@code @Bean}
* methods individually like when declared on non-{@code @Configuration} classes,
* a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
* behaviorally equivalent to removing the {@code @Configuration} stereotype.
* @since 5.2
*/
boolean proxyBeanMethods() default true;
}
proxyBeanMethods 测试
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
// 1.获取容器
SpringApplication application = new SpringApplication(BootApplication.class);
// 2.容器启动,返回启动启动后的IOC容器
ConfigurableApplicationContext context = application.run(args);
// 获取到配置类,因为配置类也是组件
UserConfig userConfig = context.getBean(UserConfig.class);
// 调用方法获取组件
User user1 = userConfig.user();
User user2 = userConfig.user();
// 根据proxyBeanMethods配置的不同来判断 多次创建的组件是否为单实例
System.out.println(user1 == user2);
}
}
proxyBeanMethods 原理
该属性的实现原理,根据这个属性名就猜到肯定是用到了代理,
如果proxyBeanMethods = true
,那么肯定会经过代理,中间会先尝试从容器中获取Bean,获取到直接返回,否则就是第一次创建,这样就保证了单实例
如果proxyBeanMethods = false
,那么就不用代理了,直接最简单调用依次创建一个
使用示例
在Spring的声明式事务中,@EnableTransactionManagement
注解会向容器中导入两个类,AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
其中引入的第2个类就用到了proxyBeanMethods ,这里设置的是false,因为事务注解@Transaction
是加在方法上的,这样每个方法的注解属性就可能不一样,所以就不能设置单例的
// 因为每个方法可能需要回滚的方式不同,所以这里使用的false
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
/** @deprecated */
@Deprecated
Autowire autowire() default Autowire.NO;
boolean autowireCandidate() default true;
// 初始化方法
String initMethod() default "";
// 销毁方法
String destroyMethod() default "(inferred)";
}
1. 该注解属于`org.springframework.beans.factory.annotation`,也就是它是Spring框架提供的
2. 默认优先按照类型去容器中找对应的组件 application.getBean(Car.class)
3. 如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找
4. @Qualifier() 使用该注解指定需要装配的组件id 而不是使用属性名
5. 自动装配一定要将属性赋值好,没有就报错
6. @Autowired(required = false) 代表找到类型赋值最好,找不到就报错
7. @Primary 当我们需要获取的对象存在多个时,我们可以通过该注解标注首选,代表哪个的优先级最高
public @interface Autowired {
// 设置是否允许Null值
boolean required() default true;
}
@Component
@Primary //代表当获取的对象存在多个时,可以通过注解标注首选
public class User {
}
如果我们要使用名称装备,@Autowired可以结合@Qualifier注解进行使用
例如: @Autowired
@Qualifier(“life”)//按照byName来自动注入
private Life life;
javax.annotation
,它是JDK提供的@Value("张三")
private String name;
@Value("#{20-2}")
private Integer age;
@Value("${spring.sex}")
private String sex;
加载配置文件
@Configuration
@PropertySource(value = {"classpath:/jdbc.properties"})
public class UserConfig {
}
默认通过@Bean注入IOC容器的对象都是单实例的,但是可以通过@Scope注解来修改
@Scope(scopeName = “prototype”) 代表多实例, 获取对象的时候才会创建
@Scope(scopeName = “singleton”) 代表单实例, IOC容器启动的时候就会创建对象
@Configuration
public class MaiConfig2 {
@Scope(scopeName = "prototype")
@Bean(name = "person2")
public Person person() {
Person person = new Person();
person.setName("王五");
person.setAge(30);
return person;
}
}
单实例bean:默认在容器启动的时候创建对象
懒加载: 容器启动不创建对象,第一次使用(获取)Bean创建对象,并初始化
@Scope(scopeName = "singleton")
@Lazy
@Bean(name = "person2")
public Person person() {
System.out.println("person 添加到 IOC容器------");
Person person = new Person();
person.setName("王五");
person.setAge(30);
return person;
}
按照一定的条件进行判断,满足条件给容器中注册,该注解可以添加到方法 / 类 中,此注解需要的值是实现Condition接口的类
1.当添加到@Bean上代表满足指定条件才注册该对象
2.当添加到类上代表满足条件才注册所有的bean
给容器中注册组件方式:
通过@Bean
指定生命周期
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod = "init",destroyMethod = "destory")
public Car car(){
return new Car();
}
}
通过接口
通过调用 InitializingBean, DisposableBean 接口来分别实现创建和销毁方法
public class Cat implements InitializingBean, DisposableBean {
public Cat(){
System.out.println("Cat 执行初始化方法");
}
@Override
public void destroy() throws Exception {
System.out.println("执行 Cat destroy方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("执行 Cat afterPropertiesSet方法");
}
}
通过注解
@PostConstruct 在bean创建完成并且属性赋值完成,来执行初始化方法
@PerDestroy 在容器销毁bean之前通过我们进行清理工作
通过后置处理器 BeanPostProcessor
这个在上面已经介绍了接口的使用
Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能
激活配置环境的方式:
该注解有2个功能:
注意点:
1.属于隐性别名组中的每一个属性必须使用@AliasFor进行注释,并且attribute必须引用相同元注解中的同一个属性
2.别名化的属性必须声明相同的返回类型
3.别名化的属性必须定义默认值
4.别名化的属性必须声明相同的默认值
5.注解必须引用合适的元注解
6.被引用的元注解必须放置在声明了@AliasFor的注解上
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface AliasFor {
// 属性的别名
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
//
Class<? extends Annotation> annotation() default Annotation.class;
}
RequestMapping示例
// 两个属性互为别名,代表设置哪个都可以
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
注解继承
像我们使用的@Controller
、@Service
、@Repository
都是继承的@Component
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
// 继承注解
@AliasFor(annotation = Component.class)
String value() default "";
}
@SpringBootApplication
是一个组合注解,里面的属性也继承到每个子注解中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
如果项目使用的XML来注册Bean,那么可以使用该注解来导入xml文件到配置类,然后就会解析当前xml文件
@Configuration(proxyBeanMethods = false)
@ImportResource(locations = "classpath:spring.xml") // 自动将xml文件注入到配置类
public class UserConfig {
@Bean
public User user() {
return new User();
}
}
实现配置绑定有两种方式
两种方式实现的功能一样,只不过是组件的注入地方,第2种方式多应用于第三方jar包,因为我们导入的jar包上面可能没有添加@Component
,同时jar包又不可以修改,所以通过@EnableConfigurationProperties
来添加
总结下来 @EnableConfigurationProperties
这个注解有两个功能,第1就是注入该组件,第2实现配置绑定
@ConfigurationProperties
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
// 前缀
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
boolean ignoreInvalidFields() default false;
boolean ignoreUnknownFields() default true;
}
方式一
第1步:需要使用Spring的功能,必须注入到容器中
地2步:通过@ConfigurationProperties注解
@Data
@Component
@ConfigurationProperties(prefix = "jia")
public class User {
private String id;
private String name;
}
方式二
第1步:添加@ConfigurationProperties注解
@Data
@ConfigurationProperties(prefix = "jia")
public class User {
private String id;
private String name;
}
第2步:配置添加@EnableConfigurationProperties
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(User.class)
public class UserConfig {
}