Java为什么企业看重Spring框架?

  Condition(条件):Condition是在Spring4.0增加的条件判断功能,通过这个可以功能可以实现选择性的创建Bean操作。

  思考:

  SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate的?

  创建一个模块,springboot-condition:

  @SpringBootApplicationpublic class SpringbootConditionApplication {public static void main(String[] args) {//启动springBoot的应用,返回spring的IOC容器ConfigurableApplicationContext context =SpringApplication.run(SpringbootConditionApplication.class, args);//获取一个Bean,RedisTemplateObject redis = context.getBean("redisTemplate");System.out.println(redis);}}

  没有引入坐标,所以没有redisTemplate的Bean。

  增加redis坐标<dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>

  redisTemplate的Bean已经引入1.1.2、Condition案例1需求:

  在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:

  1. 导入Jedis坐标后,加载该Bean,没导入,则不加载。

  2. 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。

扫描二维码关注公众号,回复: 11111129 查看本文章

  第一步:创建User实体类package com.itheima.condition.domain; public class User { }

  第二步:创建User配置类package com.itheima.condition.config;import com.itheima.condition.domain.User;import org.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@Configurationpublic class UserConfig {@Bean public User user(){ return new User(); }}

  第三步:修改启动类SpringbootConditionApplication 中增加user的Bean获取Object user = context.getBean("user"); System.out.println(user);现在是任何情况下都能加载User这个类。

  第四步:实现Condition新建一个ClassCondition类,实现Condition接口里的matches方法来控制类的加载新建一个类实现Condition接口public class ClassCondition implements Condition {@Override public boolean matches(ConditionContextconditionContext, AnnotatedTypeMetadataannotatedTypeMetadata) { return false; }}

  修改condition.config的UserConfig类:

  @Bean@Conditional(ClassCondition.class)public User user(){return new User();}

  将user对象加入Bean里的时候增加一个@Conditional注解测试:当matches返回false时,不加载User类,返回true时,加载User类第五步:导入Jedis坐标<dependency> <groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency>

  第六步:修改matches方法public boolean matches(ConditionContextconditionContext, AnnotatedTypeMetadataannotatedTypeMetadata) {boolean flag = true; try { Class<?> aClass =Class.forName("redis.clients.jedis.Jedis"); } catch (ClassNotFoundException e) { flag = false; } return flag;}

  当Jedis坐标导入后,可以加载到 redis.clients.jedis.Jedis 这个类,未导入时,加载不到redis.clients.jedis.Jedis这个类,所以通过异常捕获可以返回是否加载。

  1.1.3、Condition案例2现在的 Class.forName("redis.clients.jedis.Jedis") 这个是写死的,是否可以动态的加载呢?

  第一步:新建注解类新建ConditionOnClass注解类,增加@Conditional注解@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(ClassCondition.class)public @interface ConditionOnClass {String[] value();}

  注解类中增加value变量。

  第二步:修改UserConfig@Bean//@Conditional(ClassCondition.class)@ConditionOnClass("redis.clients.jedis.Jedis")public User user(){return new User();}

  现在新建的注解@ConditionOnClass和原注解@Conditional作用一致第三步:修改matches方法/*** @param conditionContext 上下文对象,用于获取类加载,Bean工厂等信息* @param annotatedTypeMetadata 注解的元对象,可以用于获取注解定义的属性值* @return*/@Overridepublic boolean matches(ConditionContextconditionContext, AnnotatedTypeMetadataannotatedTypeMetadata) {Map<String, Object> map =annotatedTypeMetadata.getAnnotationAttributes(ConditionOnClass.class.getName()); //System.out.println(map); String[] strings = (String[]) map.get("value"); boolean flag = true; try { for (String className : strings) { Class<?> aClass = Class.forName(className); } } catch (ClassNotFoundException e) { flag = false; } return flag;}

  此时,通过UserConfig注解注入的类存在就加载User类,如果注入的类不存在就不加载User类测试,引入fastjson坐标,加载User类第四步:查看源代码jar包org.springframework.boot.autoconfigure.condition.ConditionalOnClassorg.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration第五步:判断配置文件@Bean@ConditionalOnProperty(name = "itcast", havingValue ="itheima")public User user2(){return new User();}

  修改启动类Object user = context.getBean("user2"); System.out.println(user);在UserConfig类中新增一个方法,同样加载User类使用@ConditionalOnProperty注解,name是itcast,value是itheima修改配置文件application.properties,增加itcast=itheima测试加载User类1.1.4、Condition小结User实体类,UserConfig配置类将User放入Bean工厂,ClassCondition类重写matches方法,ConditionOnClass注解类增加注解。

  自定义条件:

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

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

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

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

  SpringBoot常用条件注解:

  ConditionalOnProperty:判断配置文件中是否有对应的属性和值才初始化BeanConditionalOnClass:判断环境中是否有对应的字节码文件才初始化BeanConditionalOnMissingBean:判断环境中是否有对应的Bean才初始化Bean。

  1.2、SpringBoot切换内置服务器1.2.1、启用内置web服务器引入starter-web坐标之后,服务器内置tomcat启动了<dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>

  1.2.2、查看一下源jar包org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer修改坐标:

  <dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions></dependency><dependency> <artifactId>spring-boot-starter-jetty</artifactId> <groupId>org.springframework.boot</groupId></dependency>

  Jetty服务器启动1.3、@Enable*注解SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载。

  问题:

  SpringBoot 工程是否可以直接获取jar包中定义的Bean?

  1.3.1、创建两个模块一个是springboot-enable,一个是springboot-enable-other1.3.2、创建实体类与配置类package com.itheima.domain;public class User {}@Configurationpublic class UserConfig {@Bean public User user(){ return new User(); }}

  1.3.3、引入enable-other坐标<dependency><groupId>com.itheima</groupId> <artifactId>springboot-enable-other</artifactId> <version>0.0.1-SNAPSHOT</version></dependency>

  1.3.4、修改enable启动类public static void main(String[] args) {ConfigurableApplicationContext context =SpringApplication.run(SpringbootEnableApplication.class,args); Object user = context.getBean("user"); System.out.println(user);}

  Bean里没有User这个类@ComponentScan扫描范围:当前引导类所在包以其子包com.itheima.enablecom.itheima.config.UserConfig两个包明显是平级的第一种:增加扫描包的范围@SpringBootApplication@ComponentScan("com.itheima.config") public class SpringbootEnableApplication {}

  第二种:使用@Import注解使用@Import注解,都会被Spring创建,放入IOC容器@SpringBootApplication@Import(UserConfig.class) public class SpringbootEnableApplication {}

  第三种:对@Import进行封装创建EnableUser注解类,使用@Import注解@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Import(UserConfig.class)public @interface EnableUser {}

  此时,查看@SpringBootApplication注解时发现,里面有@EnableAutoConfiguration注解,而这个注解中使用了@Import({AutoConfigurationImportSelector.class})注解,加入了一个类。

  1.4、@Import注解@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:

  导入Bean导入配置类导入ImportSelector实现类,一般用于加载配置文件中的类导入ImportBeanDefinitionRegistrar实现类1.4.1、导入Bean@SpringBootApplication@Import(User.class) public class SpringbootEnableApplication {}

  这样导入不了的原因在于,我们是通过Bean的名称"user"来获取对象,而导入的这个User.class不一定叫"user"这个名字,所以需要修改启动类,通过类的类型来获取。

  public static void main(String[] args) {ConfigurableApplicationContext context =SpringApplication.run(SpringbootEnableApplication.class,args); /*Object user = context.getBean("user");System.out.println(user);*/ User user = context.getBean(User.class); System.out.println(user);}

  如果想要获取Bean的名称,那么可以使用context.getBeansOfTypeMap<String, User> map =context.getBeansOfType(User.class);System.out.println(map);所以自动创建的Bean名称为com.itheima.domain.UserObject user1 =context.getBean("com.itheima.domain.User");System.out.println(user1);那么通过这个名字就可以获取这个类1.4.2、导入配置类配置类指的是前面创建好的UserConfig,那么现在再创建一个实体类package com.itheima.domain;public class Role {}

  修改UserConfig配置类,将Role这个类也加入到Bean里@Beanpublic Role role(){return new Role(); }

  修改启动类,增加Role的类加载Role role = context.getBean(Role.class); System.out.println(role);此时,两个类都可以被加载。

  1.4.3、实现ImportSelector接口创建一个MyImportSelector类,实现ImportSelector接口,重写里面的selectImports方法@SpringBootApplication@Import(MyImportSelector.class) public class SpringbootEnableApplication {}

  修改启动类@SpringBootApplication@Import(MyImportSelector.class) public class SpringbootEnableApplication {}

  此时,两个类都可以被加载。

  1.4.4、实现ImportBeanDefifinitionRegistrar接口创建一个MyImportBeanDefifinitionRegistrar类,实现ImportBeanDefifinitionRegistrar接口,重写registerBeanDefifinitions方法AbstractBeanDefinition beanDefinition =BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();registry.registerBeanDefinition("user", beanDefinition);修改启动类@SpringBootApplication@Import(MyImportBeanDefinitionRegistrar.class)public class SpringbootEnableApplication {}

  此时,User类被加载,而Role没有被加载,想要加载Role类,再次修改registerBeanDefinitions方法@Overridepublic void registerBeanDefinitions(AnnotationMetadataimportingClassMetadata, BeanDefinitionRegistry registry){//注册User类 AbstractBeanDefinition beanDefinition =BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition(); registry.registerBeanDefinition("user",beanDefinition); //注册Role类 beanDefinition =BeanDefinitionBuilder.rootBeanDefinition(Role.class).getBeanDefinition(); registry.registerBeanDefinition("role",beanDefinition);}

  而通过名称也可以将User类加入到Bean中,只需要修改启动类用名称获取Bean即可。

  1.5、@EnableAutoConfiguration@SpringBootApplication中的@EnableAutoConfiguration注解,也是通过@Import({AutoConfigurationImportSelector.class})来实现类的加载,那么说明Springboot中也是使用第三种方法导入类。

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

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

  二、SpringBoot自动配置--自定义Starter2.1、分析MyBatis加载过程引入坐标<!--https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --><dependency><groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version></dependency>

  加载过程2.2、自定义Starter需求自定义redis-starter。要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean。

  步骤:

  创建 redis-spring-boot-autoconfigure 模块创建 redis-spring-boot-starter 模块,依赖 redis-spring-boot-autoconfigure的模块在 redis-spring-boot-autoconfigure 模块中初始化 Jedis 的 Bean。并定义META-INF/spring.factories 文件在测试模块中引入自定义的 redis-starter 依赖,测试获取 Jedis 的Bean,操作 redis。

  2.3、创建两个模块redis-spring-boot-autoconfigureredis-spring-boot-starter在starter的坐标中加入autoconfigure<dependency><groupId>com.itheima</groupId> <artifactId>redis-spring-boot-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version></dependency>

  在autoconfigure的坐标中加入Jedis<dependency><groupId>redis.clients</groupId> <artifactId>jedis</artifactId></dependency>

  2.4、创建RedisAutoConfigure配置类在autoconfigure模块中创建RedisAutoConfigure配置类@Configuration@EnableConfigurationProperties(RedisProperties.class)public class RedisAutoConfigure {/** * 提供Jedis的Bean */ @Bean public Jedis jedis(RedisProperties redisProperties){ return new Jedis(redisProperties.getHost(),redisProperties.getPort()); }}

  在autoconfigure模块中创建RedisProperties配置类@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; }}

  2.5、创建META-INF/spring.factories在resources下新建META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.redis.RedisAutoConfigure2.6、修改启动类修改springboot-enable启动类@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context =SpringApplication.run(SpringbootEnableApplication.class,args);Jedis jedis = context.getBean(Jedis.class);System.out.println(jedis);}

  此时,Jedis可以加载到。

  2.7、使用Jedisjedis.set("name", "itcast"); String value = jedis.get("name"); System.out.println(value);2.8、使用配置文件修改enable里的application.properties启动时报错,连接不到本地的localhost:6666,证明redis配置文件已经起作用了。

  2.9、优化RedisAutoConfigure在RedisAutoConfigure类上增加注解@ConditionalOnClass(Jedis.class)加载的时候判断Jedis类存在的时候才加载Bean@Configuration@EnableConfigurationProperties(RedisProperties.class)@ConditionalOnClass(Jedis.class)public class RedisAutoConfigure {/** * 提供Jedis的Bean */ @Bean @ConditionalOnMissingBean(name = "jedis") public Jedis jedis(RedisProperties redisProperties){ System.out.println("RedisAutoConfigure...."); return new Jedis(redisProperties.getHost(),redisProperties.getPort()); }}

  在jedis方法上增加@ConditionalOnMissingBean注解,当jedis没有被创建时加载这个类,并写入Bean测试:

  在启动类中创建一个Jedis@Beanpublic Jedis jedis(){ return new Jedis();}

  启动的时候可以看到当Jedis存在时则不再加载。

  2.10、查看redis源jar包三、SpringBoot监听机制3.1、JAVA的监听机制SpringBoot 的监听机制,其实是对Java提供的事件监听机制的封装。

  Java中的事件监听机制定义了以下几个角色:

  ①事件:Event,继承 java.util.EventObject 类的对象②事件源:Source ,任意对象Object③监听器:Listener,实现 java.util.EventListener 接口的对象3.2、Springboot监听器SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。

  一共有四种实现方法:

  ApplicationContextInitializerSpringApplicationRunListenerCommandLineRunnerApplicationRunner3.3、创建Listener模块3.3.1、创建MyApplicationContextInitializer创建MyApplicationContextInitializer,实现ApplicationContextInitializer接口@Componentpublic class MyApplicationContextInitializer implementsApplicationContextInitializer {@Override public voidinitialize(ConfigurableApplicationContextconfigurableApplicationContext) { System.out.println("ApplicationContextInitializer...initialize"); }}

  3.3.2、创建MySpringApplicationRunListener创建MySpringApplicationRunListener,实现SpringApplicationRunListener接口@Componentpublic class MySpringApplicationRunListener implementsSpringApplicationRunListener {@Override public void starting() { System.out.println("SpringApplicationRunListener...正在启动"); } @Override public voidenvironmentPrepared(ConfigurableEnvironment environment){ System.out.println("SpringApplicationRunListener...环境准备中"); } @Override public voidcontextPrepared(ConfigurableApplicationContext context){ System.out.println("SpringApplicationRunListener...上下文准备"); } @Override public voidcontextLoaded(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...上下文加载"); } @Override public void started(ConfigurableApplicationContextcontext) { System.out.println("SpringApplicationRunListener...已经启动"); } @Override public void running(ConfigurableApplicationContextcontext) { System.out.println("SpringApplicationRunListener...正在启动中"); } @Override public void failed(ConfigurableApplicationContextcontext, Throwable exception) { System.out.println("SpringApplicationRunListener...启动失败"); }}

  3.3.3、创建MyCommandLineRunner创建MyCommandLineRunner,实现CommandLineRunner接口@Componentpublic class MyCommandLineRunner implementsCommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("CommandLineRunner...run");}

  3.3.4、创建MyApplicationRunner创建MyApplicationRunner,实现ApplicationRunner接口@Componentpublic class MyCommandLineRunner implementsCommandLineRunner {@Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run"); }}

  3.3.5、运行启动类只有MyApplicationRunner和MyCommandLineRunner运行了监听。

  @Componentpublic class MyApplicationRunner implementsApplicationRunner {@Override public void run(ApplicationArguments args) throwsException { System.out.println("ApplicationRunner...run"); }}

  3.3.6、修改监听类修改MyApplicationRunner和MyCommandLineRunner打印argsMyApplicationRunner:

  @Overridepublic void run(ApplicationArguments args) throwsException {System.out.println("ApplicationRunner...run"); System.out.println(Arrays.asList(args.getSourceArgs()));}

  MyCommandLineRunner:

  @Overridepublic void run(String... args) throws Exception { System.out.println("CommandLineRunner...run"); System.out.println(Arrays.asList(args));}

  在运行时可以将配置信息加载进来所以,这两个监听ApplicationRunner和CommandLineRunner是一样的。

  3.4、配置MyApplicationContextInitializer在模块中增加 META-INF/spring.factories 配置文件org.springframework.context.ApplicationContextInitializer=com.itheima.listener.listener.MyApplicationContextInitializer3.5、配置MySpringApplicationRunListener修改 META-INF/spring.factories 配置文件MyCommandLineRunner:@Overridepublic void run(String... args) throws Exception {System.out.println("CommandLineRunner...run"); System.out.println(Arrays.asList(args));}

  运行提示:没有MySpringApplicationRunListener的init方法所以需要查看SpringApplicationRunListener这个接口的实现类需要SpringApplication和String[]两个参数进行构造,因此修改MySpringApplicationRunListener实现类,增加构造方法public MySpringApplicationRunListener(SpringApplicationapplication, String[] args) {}

  并且去掉@Component注解此时启动正常。

  ApplicationStartedEvent继承自SpringApplicationEventSpringApplicationEvent继承自ApplicationEventApplicationEvent继承自EventObject证明:Springboot里的监听事件是对java监听事件的一个封装3.6、SpringBoot运行流程分析四、SpringBoot监控SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、配置属性、日志信息等。

  4.1、创建模块需要勾选web和ops下的SpringBootActuator坐标自动引入<dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>

  4.2、查看infoinfo获取的是配置文件以info开头的信息:

  修改propertiesinfo.name=zhangsaninfo.age=204.3、查看所有信息未开启所有明细状态:

  修改propertiesmanagement.endpoint.health.show-details=always开启所有明细状态:

  4.4、暴露所有的信息修改properties:

  management.endpoints.web.exposure.include=*

http://www.lyxbyby.net/rl/slgjys/18098.html    http://m.lyxbyby.net/nk/qlxjb/jxqlxy/20200424/258.html   http://www.sxlyxyy.com/ksjs/nk/323.html   http://www.62639999.com/nvby/pqxby/18112.html

猜你喜欢

转载自www.cnblogs.com/javaahb/p/12768145.html