知识点:BeanPostProcessor和ApplicationListener
Spring初阶知识复习
说到spring,首先被提及的就是IOC和AOP ,这里面都有哪些知识点呢?
1.spring启动过程、看源码装B。(加载bean定义BeanDefinition,创建bean工厂等)---背源码没什么意义,看了忘,忘了看,就怕面试而已
2.bean的作用域(单例、多例),bean的生命周期:1.实例化 2.初始化 3.使用销毁 【中间包括属性赋值,也就是依赖注入,还有aware接口拓展(BeanNameAware),BeanPostProcessor处理器等】
(1)spring默认是单例的,所有加入spring容器的bean都维护在一个单例池中。
(2)bean的初始化有三种方法:@PostConstruct>实现InitializingBean方法afterPropertiesSet>init-method参数指定
(3)依赖注入的方式四种:构造器注入、set方法注入、属性注入(autowire或者resorce)、接口注入(不常用)
3.注解:@Restcontroller/@Controller、@getMapping/@RequestMapping/@Pathvariable、@Autowired/@Resource、Scope、RequestBody/ResponseBody、@Bean
(4.1)@Restcontroller/@Controller区别?
(4.2)@RequestMapping中path和value属性的区别?
(4.3)@Autowired/@Resource的区别?
4.认识spring容器BeanFactory 和ApplicationContext ,前者实现基础的功能,后者功能更多,包括AOP。前者getBean的时候才实例化,后者启动后全部实例化bean。
5.spring解决循环依赖:三级缓存 singletonObjects/earlySingletonObjects/singletonFactories
6.springmvc过程、拦截器/过滤器区别
7.AOP原理动态代理,说到动态代理JDK和CGLIB,默认JDK。前者是反射对实现接口的类生成代理,后者是利用字节码技术生成子类覆盖。
说到动态代理,spring事务就是最典型的动态代理。 @Transitional失效的情况?
8. springboot改进:自动配置、起步依赖、内置tomcat、Actuator运行监控。springboot自动配置原理?
以上基本已经踩了一遍spring一些关键点,初步认识了Spring!!
正片开始!!!
实战:利用BeanPostProcessor和ApplicationListener检查API接口是否有新增
背景是这样的,在微服务中RequestMapping形成一个唯一标识的接口,上线一个新的接口需要进行对外API发布,如果API没有对外发布,外面是调不通的。如果有新来的开发人员接了一个新接口,但是忘记了通知维护进行API发布,那么上线就白上了。
因此,可以在spring启动的时候持久化一次API接口(文件/数据库),下次再上线的时候,程序在测试环境启动就可以检查到是否有新增API,具体怎么做呢?
第一步:新增MyBeanPostProcessor 实现BeanPostProcessor
import org.apache.servicecomb.provider.rest.common.RestSchema;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//初始化前执行当前方法
//doWithMethos的功能是可以执行类中的所有方法,并且提供回调函数方法
Class<?> cl = bean.getClass();
RestSchema restSchema = cl.getAnnotation(RestSchema.class);
String classRequestMappingValue = "";
if (restSchema != null) {
List<String> valueList = new ArrayList<>();
RequestMapping classMapping = cl.getAnnotation(RequestMapping.class);
if (classMapping != null) {
classRequestMappingValue = classMapping.path()[0];
MyCache.mappingMapList.put(classRequestMappingValue, valueList);
}
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
MyBeanPostProcessor.this.processConsumerMethod(bean, method, valueList);
}
});
}
return bean;
}
private void processConsumerMethod(Object bean, Method method, List<String> valueList) {
//获取方法上的注解
if (method != null) {
RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
if (methodRequestMapping != null) {
String[] values = methodRequestMapping.value();
if (values != null && values.length > 0) {
valueList.add(values[0]);
}
}
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//初始化后执行当前方法 ,可以做什么事情?
// 1.修改注解中的属性值,比如增加前缀标识,比如有调用多个公司的项目,可以增加分支标识自己的公司,这样就可以区分
//获取Class对象中的注解信息
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
讲一下关键内容:
1. postProcessBeforeInitialization 这个就是初始化之前的操作,postProcessAfterInitialization是初始化后的操作。
2.主要利用Class对象获取里面的注解信息,getAnnotation()方法,RestSchema注解是一个类的唯一标识(这是servicecomb开源微服务框架的注解),相当于类RequestMapping。
3.ReflectionUtils.doWithMethods 这是spring自带的反射工具类,什么用呢?主要是可以对一个类的所有方法进行遍历处理,为的就是可以得到方法上的注解。
org.springframework.util.ReflectionUtils部分源码
public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
// Keep backing up the inheritance hierarchy.
Method[] methods = getDeclaredMethods(clazz);
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
if (clazz.getSuperclass() != null) {
doWithMethods(clazz.getSuperclass(), mc, mf);
}
else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
}
}
}
4.MethodCallback 是一个回调接口,我这里就是对获取到RequestMapping注解后做的一些操作,放到一个List里。
5.Ordered 是spring的一个接口,有什么用呢?BeanPostProcessor是spring开放给我们的接口,可以有多个实现类,Ordered的作用就是决定这些实现类的执行顺序啦,值越小越早执行。我这里是无所谓先后。
第二步:MyApplicationListener 实现ApplicationListener <ContextRefreshedEvent>
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class MyApplicationListener implements ApplicationListener <ContextRefreshedEvent>{
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent()==null){//保证只执行一次
System.out.println("============================================spring容器启动完毕,现在再做一些自己的操作!");
new Thread(new ApiCheck()).start();
}
}
}
讲解:ApplicationListener 这里的作用是在spring容器加载完以后,如果还需要做什么操作,就实现这个接口。而在springboot中提供了ApplicationRunner接口来实现这样的操作。
第三步:业务操作ApiCheck.java
public class MyCache {
public static Map<String,List<String>> mappingMapList = new HashMap<>();
public static Map<String, List<String>> getMappingMapList() {
return mappingMapList;
}
public static void setMappingMapList(Map<String, List<String>> mappingMapList) {
MyCache.mappingMapList = mappingMapList;
}
}
import lombok.SneakyThrows;
import org.apache.servicecomb.core.SCBEngine;
import org.apache.servicecomb.core.SCBStatus;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class ApiCheck implements Runnable {
@SneakyThrows
@Override
public void run() {
for (; ; ) {
SCBStatus currentStatus = SCBEngine.getInstance().getStatus();
if (currentStatus.equals(SCBStatus.UP)) {//如果一开始就改成UP,启动流程还没走完,发现是UP,不会往下走
Map<String, List<String>> mappingMapList = MyCache.getMappingMapList();
List<String> apiListNew = new ArrayList<>();
for (Map.Entry<String, List<String>> stringListEntry : mappingMapList.entrySet()) {
String schemaId = stringListEntry.getKey();
List<String> list = stringListEntry.getValue();
for (String s : list) {
apiListNew.add(schemaId+s);
}
}
File file = new File("C:\\....\\api.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "gbk"));
String s = "";
List<String> apiOldList = new ArrayList<>();
while (( s = bufferedReader.readLine()) != null) {
apiOldList.add(s);
}
for (String s1 : apiListNew) {
boolean haveFlag = false;
for (String s2 : apiOldList) {
if(s1.equals(s2)){
haveFlag = true;
break;
}
}
if(!haveFlag){
System.out.println("------------api不存在:"+s1);
}
}
System.out.println("============================================API检查分析完毕!");
break;
}
TimeUnit.SECONDS.sleep(3);
}
}
}
api.txt
/aa/query
/bb/insert
/cc/delete
/dd/xxquery
讲解: 大体上就是spring启动时把api放到一个缓存map里,然后跟持久化的api.txt进行比较,如果发现比api.txt里面多,说明新增了API,达到检查的目的,功能完成!!
效果图:
总结:你要知道的
- 基本知道BeanPostProcessor 和ApplicationListener、ApplicationRunner的用法
- 知道ReflectionUtils.doWithMethods的工具
- 认识MethodCallback回调接口
- 认识Ordered接口的作用
- Class对象如何获取类上的注解和方法上的注解
虽然这可能不算Spring中高阶,但至少跟别人说的时候,不再只是IOC、AOP,权限控制反转这些理论知识了,我要加油!!!参与了spring的启动,迈出了一步!!