这个是Spring Boot提供扩展接口,这个接口也只有一个方法:run(String... args),触发时机为整个项目启动完毕后,自动执行。如果有多个 CommandLineRunner,可以利用 @Order注解 来进行排序,值越小越优先执行。使用场景:用户扩展此接口,进行启动项目之后一些业务的预处理。
@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner run args:" args);
}
}
@Component
@Order(0)
public class MyTwoCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyTwoCommandLineRunner run args:" args);
}
}
ApplicationListener 可以监听某个事件的 event,触发时机可以穿插在业务方法执行过程中,用户可以自定义某个业务事件;但是 spring 内部也有一些内置事件,这种事件,可以穿插在启动调用中。我们也可以利用这个特性,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。接下来罗列下 spring 主要的内置事件:
· ContextRefreshedEvent
· ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。此处的初始化是指:所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有 Singleton Bean 被预实例化,ApplicationContext 容器已就绪可用。
· ContextStartedEvent
· 当使用 ConfigurableApplicationContext (ApplicationContext 子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
· ContextStoppedEvent
· 当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作
· ContextClosedEvent
· 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启
· RequestHandledEvent
· 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用 DispatcherServlet 的 Web 应用。在使用 Spring 作为前端的 MVC 控制器时,当 Spring 处理用户请求结束后,系统会自动触发该事件
@Configuration
public class MyApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
System.out.println("MyApplicationListener onApplicationEvent");
}
}
BeanNameAware
BeanNameAware也是 Aware 接口扩展的子接口,触发点在 bean 的初始化之前,也就是 postProcessBeforeInitialization 之前
如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;这个类的触发点方法只有一个:setBeanName使用场景为:用户可以扩展这个点,在初始化 bean 之前拿到 spring 容器中注册的的 beanName,来自行修改这个 ;
@Component
public class MyBeanNameAware implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("setBeanName:" s);
}
}
BeanFactoryAware
BeanFactoryAware也是 Aware 接口扩展的子接口,只有一个触发点,发生在 bean 的实例化之后,注入属性之前,也就是 Setter 之前。这个类的扩展点方法为 setBeanFactory,可以拿到 BeanFactory 这个属性。使用场景为,你可以在 bean 实例化之后,但还未初始化之前,拿到 BeanFactory,在这个时候,可以对每个 bean 作特殊化的定制。也或者可以把 BeanFactory 拿到进行缓存,日后使用,如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;
@Component
public class MyBeanFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("MyBeanFactoryAware setBeanFactory:" beanFactory.getBean("myBeanFactoryAware").getClass().getSimpleName());
}
}
Bean初始化及销毁回调方法
- InitializingBean是用来初始化 bean 的。InitializingBean 接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在初始化 bean 的时候都会执行该方法。这个扩展点的触发时机在 postProcessAfterInitialization 之前。
- DisposableBean这个扩展点也只有一个方法:destroy(),其触发时机为当此对象销毁时,会自动执行这个方法。比如说运行 applicationContext.registerShutdownHook 时,就会触发这个方法。
- @PostConstruct这个并不算一个扩展点,其实就是一个标注。其作用是在 bean 的初始化阶段,如果对一个方法标注了 @PostConstruct,会先调用这个方法。这里重点是要关注下这个标准的触发点,这个触发点是在 postProcessBeforeInitialization 之后,InitializingBean.afterPropertiesSet 之前。使用场景:用户可以对某一方法进行标注,来进行初始化某一个属性
- @PreDestroy修饰的方法会在服务器关闭Spring容器的时候运行,并且只会调用一次
使用场景:用户实现此接口,来进行系统启动的时候一些业务指标的初始化工作。
@Repository
public class Student implements InitializingBean, DisposableBean {
@PostConstruct
public void init() throws Exception {
System.out.println("Student init");
}
@PreDestroy
public void close() throws Exception {
System.out.println("Student close");
}
@Override
public void destroy() throws Exception {
System.out.println("Student destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Student afterPropertiesSet");
}
}
mybatis-spring gitee源码地址
https://gitee.com/yongzhebuju/mybatis-spring
我们从这些 Spring&Spring Boot 的扩展点当中,大致可以窥视到整个 bean 的生命周期。在业务开发或者写中间件业务的时候,可以合理利用 Spring 提供给我们的扩展点,在 Spring 启动的各个阶段内做一些事情,以达到自定义初始化的目的。接下来我们一起学习一个常见Mybatis和Spring整合开发简易示例,上面内容我们初步对于Spring扩展点有了一些理解,由于本人阅读过部分Mybatic与Spring整合源码,思路也是来源于此,由于目前是简易示例,不在工程加入Mybatis依赖,着重在整合部分
思考问题- Spring的Bean是如何生成的?
- Spring提供哪些扩展点来整合第三方框架?
- Spring是如何来整理Mybatis的?Mybatis代理对象都是接口,不能直接通过New方式将Mapper Bean注册Spring容器里
- 首先可以明确一点,我们需要利用到Jdk动态代理及反射机制
- 借助FactoryBean特性,FactoryBean可以返回动态代理的对象及类型
- 通过ImportBeanDefinitionRegistrar通过Import注解将FactoryBean通过BeanDefinition注册到BeanDefinitionRegistry通过后续Bean的生命周期最终放到Spring的容器里
从官网的就可以直接找到Java编程快速入门** ,这里只是大概说明一下**
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
整合示例实现
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itxs</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
</dependencies>
</project>
注解接口,通过@Import同时将ItxsImportBeanDefinitionRegistrar导入到Spring容器里
@Retention(RetentionPolicy.RUNTIME)
@Import({ItxsImportBeanDefinitionRegistrar.class})
public @interface ItxsScan {
String value() default "";
}
配置类,@ItxsScan为我们自定义的注解,主要标记扫描mapper路径
@Configuration
@ComponentScan({"com.itxs"})
@ItxsScan("com.itxs.dao")
public class AppConfig {
}
mapper接口,@ItxsSelect为我们自定义的注解,主要标记mapper接口方法sql语句
package com.itxs.dao;
import com.itxs.annotation.ItxsSelect;
import com.itxs.pojo.User;
public interface UserMapper {
@ItxsSelect("select * from user where user_id = #{userId}")
User selectByUserId(Integer userId);
}
service实现类
@Component
public class UserService {
@Autowired
UserMapper userMapper;
@Autowired
OrderMapper orderMapper;
public void getUser(){
userMapper.selectByUserId(1);
orderMapper.selectByOrderId(1);
}
}
FactoryBean实现,这里通过构造函数传入接口的Class类型,将类型通过Jdk动态代理生成并返回对象,当调用目标对象后会执行代理对象invoke方法,从invoke方法通过反射与注解获取到sql语句,后续流程就可以利用Mybatis提供操作数据库流程,这里就不继续深入了
package com.itxs.utils;
import com.itxs.annotation.ItxsSelect;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ItxsFactoryBean implements FactoryBean {
private Class mapper;
public ItxsFactoryBean(Class mapper) {
this.mapper = mapper;
}
@Override
public Object getObject() throws Exception {
//使用动态代理机制
Object o = Proxy.newProxyInstance(ItxsFactoryBean.class.getClassLoader(), new Class[]{mapper}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())){
return method.invoke(this,args);
}
ItxsSelect annotation = method.getAnnotation(ItxsSelect.class);
System.out.println("调用方法名称是" method.getName() ",sql语句为" annotation.value());
//后面可执行Mybatic操作数据库的相关操作
return null;
}
});
return o;
}
@Override
public Class<?> getObjectType() {
return mapper;
}
}
ImportBeanDefinitionRegistrar的实现,通过获取@import上的注解找到mapper的扫描路径,通过classLoader加载磁盘下Class文件生成BeanDefinition并设置构造函数mapper类型参数,最终将BeanDefinition注册到BeanDefinitionRegistry
package com.itxs.utils;
import com.itxs.annotation.ItxsScan;
import com.itxs.dao.OrderMapper;
import com.itxs.dao.UserMapper;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ItxsImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
//获取注解ItxsScan对象,并取出value值作为扫描的路径
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ItxsScan.class.getName());
String mapperPath = annotationAttributes.get("value").toString();
List<Class> mappers = scan(mapperPath);
for (Class mapper : mappers) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
beanDefinition.setBeanClass(ItxsFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapper);
registry.registerBeanDefinition(StringUtils.toLowerCaseFirstOne(mapper.getSimpleName()),beanDefinition);
}
}
private List<Class> scan(String path) {
List<Class> classList = new ArrayList<Class>();
path = path.replace(".", "/");
ClassLoader classLoader = ItxsImportBeanDefinitionRegistrar.class.getClassLoader();
URL url = classLoader.getResource(path);
File file = new File(url.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i ) {
String absolutePath = files[i].getAbsolutePath();
absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\", ".");
try {
Class<?> aClass = classLoader.loadClass(absolutePath);
classList.add(aClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
return classList;
}
}
Main程序
package com.itxs;
import com.itxs.config.AppConfig;
import com.itxs.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MybatisApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.getUser();
}
}
运行结果