浅谈springboot自动装配原理

网友投稿 301 2022-10-24


浅谈springboot自动装配原理

一、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 {

@Target(ElementType.TYPE)

设置当前注解可以标记在哪里,而SpringBootApplication只能用在类上面

还有一些其他的设置

public enum ElementType {

/** Class, interface (including annotation type), or enum declaration */

TYPE,

/** Field declaration (includes enum constants) */

FIELD,

/** Method declaration */

METHOD,

/** Formal parameter declaration */

PARAMETER,

/** Constructor declaration */

CONSTRUCTOR,

/** Local variable declaration */

LOCAL_VARIABLE,

/** Annotation type declaration */

ANNOTATION_TYPE,

/** Package declaration */

PACKAGE,

/**

* Type parameter declaration

*

* @since 1.8

*/

TYPE_PARAMETER,

/**

* Use of a type

*

* @since 1.8

*/

TYPE_USE,

/**

* Module declaration.

*

* @since 9

*/

MODULE

}

@Retention(RetentionPolicy.RUNTIME)

public enum RetentionPolicy {

/**

* Annotations are to be discarded by the compiler.

*/

SOURCE,

/**

* Annotations are to be recorded in the class file by the compiler

* but need not be retained by the VM at run time. This is the default

* behavior.

*/

CLASS,

/**

* Annotations are to be recorded in the class file by the compiler and

* retained by the VM at run time, so they may be read reflectively.

*

* @see java.lang.reflect.AnnotatedElement

*/

RUNTIME

}

SOURCE 当编译时,注解将不会出现在class源文件中

CLASS 注解将会保留在class源文件中,但是不会被jvm加载,也就意味着不能通过反射去找到该注解,因为没有加载到java虚拟机中

RUNTIME是既会保留在源文件中,也会被虚拟机加载

@Documented

java doc 会生成注解信息

@Inherited

是否会被继承,就是如果一个子类继承了使用了该注解的类,那么子类也能继承该注解

@SpringBootConfiguration

标注在某个类上,表示这是一个Spring Boot的配置类,本质上也是使用了@Configuration注解

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Configuration

public @interface SpringBootConfiguration {

@EnableAutoConfiguration

@EnableAutoConfiguration告诉SpringBoot开启自动配置,会帮我们自动去加载 自动配置类

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import({AutoConfigurationImportSelector.class})

public @interface EnableAutoConfiguration {

@AutoConfigurationPackage

将当前配置类所在包保存在BasePackages的Bean中。供Spring内部使用

@Import({AutoConfigurationImportSelector.class})来加载配置类

配置文件的位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当springboot启动时,会自动加载这些配置类,初始化Bean

并不是所有Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean

二、案例

自定义redis-starter,要求当导入redis坐标时,spirngboot自动创建jedis的Bean

步骤

1.创建redis-spring-boot-autoconfigure模块

2.创建redis-spring-boot-starter模块,依赖redis-spring-boot-autoconfigure的模块

3.在redis-spring-boot-autoconfigure模块中初始化jedis的bean,并定义META-INF/spring.factories文件

4.在测试模块中引入自定义的redis-starter依赖,测试获取jedis的bean,操作redis

1.首先新建两个模块

删除一些没有用的东西,和启动类否则会报错

2.redis-spring-boot-starter模块的pom.xml里面引入redis-spring-boot-autoconfigure的模块的坐标

3.RedisAutoConfiguration配置类

package com.blb;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import redis.clients.jedis.Jedis;

@Configuration

@EnableConfigurationProperties(RedisProperties.class)

public class RedisAutoConfiguration {

// 提供Jedis的bean

@Bean

public Jedis jedis(RedisProperties redisProperties){

return new Jedis(redisProperties.getHost(),redisProperties.getPort());

}

}

RedisProperties

package com.blb;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "redis")

public class RedisProperties {

private String host="localhost";

private int port=6379;

public String getHost() {

return host;

}

public void setHost(String host) {

this.host = host;

}

public int getPort() {

return port;

}

public void setPort(int port) {

this.port = port;

}

}

@ComponentScan

扫描包 相当于在spring.xml 配置中context:comonent-scan 但是并没有指定basepackage,如果没有指定spring底层会自动扫描当前配置类所有在的包

排除的类型

public enum FilterType {

/**

* Filter candidates marked with a given annotation.

* @see org.springframework.core.type.filter.AnnotationTypeFilter

*/

ANNOTATION,

/**

* Filter candidates assignable to a given type.

* @see org.springframework.core.type.filter.AssignableTypeFilter

*/

ASSIGNABLE_TYPE,

/**

* Filter candidates matching a given AspectJ type pattern expression.

* @see org.springframework.corhttp://e.type.filter.AspectJTypeFilter

*/

ASPECTJ,

/**

* Filter candidates matching a given regex pattern.

* @see org.springframework.core.type.filter.RegexPatternTypeFilter

*/

REGEX,

/** Filter candidates using a given custom

* {@link org.springframework.core.type.filter.TypeFilter} implementation.

*/

CUSTOM

}

ANNOTATION 默认根据注解的完整限定名设置排除

ASSIGNABLE_TYPE 根据类的完整限定名排除

ASPECTJ 根据切面表达式设置排除

REGEX 根据正则表达式设置排除

CUSTOM 自定义设置排除

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

按照自定义的方式来排除需要指定一个类,要实现TypeFilter接口,重写match方法

public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {

if (this.beanFactory instanceof ListableBeanFactory && this.getClass() == TypeExcludeFilter.class) {

Iterator var3 = this.getDelegates().iterator();

while(var3.hasNext()) {

TypeExcludeFilter delegate = (TypeExcludeFilter)var3.next();

if (delegate.match(metadataReader, metadataReaderFactory)) {

return true;

}

}

}

return false;

}

}

TypeExcludeFilter :springboot对外提供的扩展类, 可以供我们去按照我们的方式进行排除

AutoConfigurationExcludeFilter :排除所有配置类并且是自动配置类中里面的其中一个

示例

package com.blb.springbootyuanli.config;

import org.springframework.boot.context.TypeExcludeFilter;

import org.springframework.core.type.classreading.MetadataReader;

import org.springframework.core.type.classreading.MetadataReaderFactory;

import java.io.IOException;

public class MyTypeExcludeFilter extends TypeExcludeFilter {

@Override

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {

if(metadataReader.getClassMetadata().getClass()==UserConfig.class){

return true;

}

return false;

}

}

三、Condition

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean,实现选择性的创建bean的操作,该注解为条件装配注解

源码

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Conditional {

/**

* All {@link Condition} classes that must {@linkplain Condition#matches match}

* in order for the component to be registered.

*/

Class extends Condition>[] value();

}

@FunctionalInterface

public interface Condition {

/**

* Determine if the condition matches.

* @param context the condition context

* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}

* or {@link org.springframework.core.type.MethodMetadata method} being checked

* @return {@code true} if the condition matches and the component can be registered,

* or {@code false} to veto the annotated component's registration

*/

boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

重写matches方法如果返回true spring则会帮你创建该对象,否则则不会

springboot提供的常用条件注解

@ConditionalOnProperty:判断文件中是否有对应属性和值才实例化Bean

@ConditionalOnClass 检查类在加载器中是否存在对应的类,如果有则被注解修饰的类就有资格被 Spring 容器所注册,否则会被跳过。

@ConditionalOnBean 仅仅在当前上下文中存在某个对象时,才会实例化一个 Bean

@ConditionalOnClass 某个 CLASS 位于类路径上,才会实例化一个 Bean

@ConditionalOnExpression 当表达式为 true 的时候,才会实例化一个 Bean

@ConditionalOnMissingBean 仅仅在当前上下文中不存在某个对象时,才会实例化一个 Bean

@ConditionalOnMissingClass 某个 CLASS 类路径上不存在的时候,才会实例化一个 Bean

案例

在springIOC容器中有一个User的bean,现要求:

引入jedis坐标后,加载该bean,没导入则不加载

实体类

package com.blb.springbootyuanli.entity;

public class User {

private String name;

private int age;

get/set

UserConfig

配置类

package com.blb.springbootyuanli.config;

import com.blb.springbootyuanli.condition.UserCondition;

import com.blb.springbootyuanli.entity.User;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Conditional;

import org.springframework.context.annotation.Configuration;

@Configuration

public class UserConfig {

@Bean

@Conditional(UserCondition.class)

public User user(){

return new User();

}

}

UserCondition

实现Condition接口,重写matches方法

package com.blb.springbootyuanli.condition;

import org.springframework.context.annotation.Condition;

import org.springframework.context.annotation.ConditionContext;

import org.springframework.core.type.AnnotatedTypeMetadata;

public class UserCondition implements Condition {

@Override

public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

//思路判断jedis的class文件是否存在

boolean flag=true;

try {

Class> aClass = Class.forName("redis.clients.jedis.Jedis");

} catch (ClassNotFoundException e) {

flag=false;

}

return flag;

}

}

启动类

package com.blb.springbootyuanli;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication

public class SpringbootYuanliApplication {

public static void main(String[] args) {

ConfigurableApplicationContext app = SpringApplication.run(SpringbootYuanliApplication.class, args);

Object user = app.getBean("user");

System.out.println(user);

}

}

当我们在pom.xml引入jedis的坐标时,就可以打印user对象,当删除jedis的坐标时,运行就会报错 No bean named ‘user' available

四、案例升级

将类的判断定义为动态的,判断那个字节码文件可以动态指定

自定义一个注解

添加上元注解

package com.blb.springbootyuanli.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD}) //该注解的添加范围

@Retention(RetentionPolicy.RUNTIME) //该注解的生效时机

@Documented //生成javadoc的文档

@Conditional(UserCondition.class)

public @interface UserClassCondition {

String[] value();

http://}

UserConfig

package com.blb.springbootyuanli.config;

import com.blb.springbootyuanli.condition.UserClassCondition;

import com.blb.springbootyuanli.entity.User;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class UserConfig {

@Bean

//@Conditional(UserCondition.class)

@UserClassCondition("redis.clients.jedis.Jedis")

public User user(){

return new User();

}

}

package com.blb.springbootyuanli.condition;

import org.springframework.context.annotation.Condition;

import org.springframework.context.annotation.ConditionContext;

import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

public class UserCondition implements Condition {

/**

*

* @param context 上下文对象,用于获取环境,ioc容器,classloader对象

* @param metadata 注解元对象。可以获取注解定义的属性值

* @return

*/

@Override

public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

//思路判断指定属性的class文件是否存在

//获取注解属性值 value

Map map=metadata.getAnnotationAttributes(UserClassCondition.class.getName());

String[] values= (String[])map.get("value");

boolean flag=true;

try {

for(String classname:values){

Class> aClass = Class.forName(classname);

}

} catch (ClassNotFoundException e) {

flag=false;

}

return flag;

}

}

测试自带的注解

package com.blb.springbootyuanli.config;

import com.blb.springbootyuanli.entity.User;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class UserConfig {

@Bean

//@Conditional(UserCondition.class)

//@UserClassCondition("redis.clients.jedis.Jedis")

@ConditionalOnProperty(name="age",havingValue = "18")

//只有在配置文件中有age并且值为18spring在能注册该bean

public User user(){

return new User();

}

}

五、小结

自定义条件:

1.定义条件类:自定义类实现Condition接口,重写重写matches方法,在matches方法中进行逻辑判断,返回boolean值

2.matches方法的两个参数:

context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory

metadata:元数据对象,用于获取注解属性

3.判断条件:在初始化Bean时,使用@Conditional(条件类.class) 注解


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:单位网络布线中需要注意的几个问题及心得
下一篇:TCP/UDP,动态路由,RIP/OSPF/BGP简述
相关文章

 发表评论

暂时没有评论,来抢沙发吧~