SpringBoot Starters 教程

一. SpringBoot Starters 是什么

如果没有SpringBoot Starters ,我们要开发一个web项目,要用到:Spring MVC、Tomcat 和 Jackson 等大量的库。如果用SpringBoot Starters ,就只要依赖一个SpringBoot Starters 就把上面web相关的jar都依赖进来了,而且进行自动加载到spring(自动配置)。

springboot内置了web开发的starts:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

SpringBoot Starters 就是一个组合 依赖包的 包。引入starts就间接把相关的包都引进来了。大大简化了我们的配置。


二. SpringBoot自动配置 (SPI机制)

SpringBoot Starters 只是集中了依赖包。但是并没有将bean加载到spring容器。SpringBoot自动配置 就是可以将指定bean进行加载到容器。

springboot在加载的时候,会扫描环境变量下的 META-INF/spring.factories 文件,比如有个配置如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.uc.A,com.uc.B

A , B 类 , 必须配置@Configuration注解:

@Configuration
public class A {
   @Bean
   public Person person {
     return new Person();
   }

}
@Configuration
publi class B {

}

SpringBoot 底层会构建一个Map结构, key为org.springframework.boot.autoconfigure.EnableAutoConfiguration , 值为一个list,A,B 类。 然后 将这个map所有的value都通过反射 实例化 并注入到Spring容器里面。

这就是 SPI机制 。

自动配置原理+实践 请见:

罗政:不指定扫描包 框架第三方Jar包的Bean如何注入Spring (微服务监控实例)

SPI机制详解:

罗政:Java SPI+Dubbo SPI+SpringBoot SPI 原理解析

三. 自己动手做 starts

需求: 将我们每一个controller请求的信息(参数,返回值,url等) 异步上报的采集中心。

思想:

  • 获取注入到spring的所有controller实例。
  • 对controller实例进行动态代理,拦截controller调用方法,在controller方法调用之后 取出参数,返回值,当前请求url异步上传给采集服务。

下面开始动手吧~~~


我们新建一个 webmonitor-spring-boot-starter 项目 , maven配置如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.hadluo</groupId>
	<artifactId>webmonitor-spring-boot-starter</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<!-- 换成自己公司的parent -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.10</version>
	</parent>

	<dependencies>
		<!-- 依赖 starter web -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>
</project>

项目命名tips:

starts是有命名规范的
spring官方提供的starts命名规范spring-boot-starter-* 
第三方提供的starts, 命名  *-spring-boot-starter 

首先新建自动配置类:

package com.hadluo.webmonitor;

@Configuration
public class WebMonitorAutoConfigure {
	@Bean
	public WebMonitor webMonitor() {
               // 真正实现代理controller业务
		return new WebMonitor();
	}
}

根据springboot的自动配置功能,会扫描到WebMonitorAutoConfigure ,发现有内部有@Bean修饰的方法,于是将WebMonitor 类也注入到bean。

META-INF/spring.factories 内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hadluo.webmonitor.WebMonitorAutoConfigure

WebMonitor类实现

先把代码贴出来:

public class WebMonitor implements BeanPostProcessor {
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean.getClass().isAnnotationPresent(RestController.class)
				|| bean.getClass().isAnnotationPresent(RestController.class)) {
			//我们只对controller类进行代理
			return createProxy(bean);
		}
		return bean;
	}

	private Object createProxy(Object bean) {
		/**
		 * cglib动态代理
		 *
		 * @param object 被代理类对象
		 * @return 代理实例
		 */
		// 模拟拦截器
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(bean.getClass());
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
				// 调用controller方法
				Object result = proxy.invokeSuper(obj, args);
				// 异步上传到 采集服务器
				try {
					ayncUpload(args, result, getUrl());
				} catch (Throwable e) {
					// 不影响controller层业务
				}
				return result;
			}
		});
		return enhancer.create();
	}

	private void ayncUpload(Object[] args, Object result, String url) {
		System.out.println("*******开始异步上传到采集服务*******");
		System.out.println("url:" + url);
		System.out.println("参数:" + Arrays.asList(args));
		System.out.println("返回值:" + result);
		System.out.println("*******完成异步上传到采集服务*******");
	}

	private String getUrl() {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		String url = "";
		url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
				+ request.getServletPath();
		if (request.getQueryString() != null) {
			url += "?" + request.getQueryString();
		}
		return url;
	}
}

上面核心三个东西:

  • BeanPostProcessor的用法。
  • spring的cglib代理的使用。
  • RequestContextHolder 静态方法获取HttpServletRequest。


BeanPostProcessor

public interface BeanPostProcessor {
        // 实例化bean之前
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
         // 实例化bean之后
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

如上接口声明所示,BeanPostProcessor接口有两个回调方法。当一个BeanPostProcessor的实现类注册到Spring IOC容器后,对于该Spring IOC容器所创建的每个bean实例在初始化方法(如afterPropertiesSet和任意已声明的init方法)调用前,将会调用BeanPostProcessor中的postProcessBeforeInitialization方法,而在bean实例初始化方法调用完成后,则会调用BeanPostProcessor中的postProcessAfterInitialization方法,整个调用顺序可以简单示意如下:

--> Spring IOC容器实例化Bean
--> 调用BeanPostProcessor的postProcessBeforeInitialization方法
--> 调用bean实例的初始化方法
--> 调用BeanPostProcessor的postProcessAfterInitialization方法

可以看到,Spring容器通过BeanPostProcessor给了我们一个机会对Spring管理的bean进行再加工。

详细BeanPostProcessor用法请见:

罗政:如何改变bean的行为? - BeanPostProcessor+动态代理 实践

我们可以在postProcessBeforeInitialization里面对spring实例化的bean进行加工。设置一层代理,然后把代理对象返回给spring容器,完成移花接木。

CGLIB代理

为什么要用这个而不是jdk原生的动态代理呢?原因就是cglib代理的类不是接口也行,而我们的controller恰好不是接口,jdk动态代理是无法实现的。

我们看下我们实现的cglib代理的代码:

enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
				// 调用controller方法
				Object result = proxy.invokeSuper(obj, args);
				// 异步上传到 采集服务器
				try {
					ayncUpload(args, result, getUrl());
				} catch (Throwable e) {
					// 不影响controller层业务
				}
				return result;
			}
		});

MethodInterceptor为方法的拦截,也就是调用代理类的方法时,会回调intercept方法。我们先通过intercept 执行代理controller的方法。然后拿到返回值,参数,url等异步上传采集服务。


这样我们就完成了上述功能,接下来 测试。

随便编写一个controller,在我们springboot初始项目里面:

@RestController
public class OrderController {

	@GetMapping("/order/get")
	public String get(@RequestParam("id") Integer id) {
		System.out.println("OrderController 方法执行 :" + id);
		return "我是订单数据返回值";
	}
}

springboot初始项目 生成请见:

罗政:SpringBoot 初始项目自动生成

启动,访问输出:

我们通过代理controller的方式,完成了对controller方法调用的监控。


推荐一个Java架构师博客,带你一起写架构:

JAVA架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者