文章目录
代理模式
1. 代理模式
目标:理解什么是代理,以及它的作用
1.1 何为代理?
代理是通过代理对象访问目标对象,这样可以在目标对象的逻辑进行增强,或者称之为改变。
代理分为静态代理和动态代理两种。
1.2 组成结构
代理逻辑中一般包含一个目标对象和一个代理对象。这个怎么理解呢?
举个例子:这就好比一部日本拍了一部小电影(demo.avi),但是电影可能还要加入一些马赛克才能发行,最后成为一部新的电影(demo2.avi),那这个demo.avi就相当于目标对象,而这个demo2.avi就相当于代理对象。demo2是对demo1内容的增强,或者称之为改变。
代码样例
-
目标类:
-
package cn.itcast.show.inherit; /** * 目标类 * * @Author LK * @Date 2020/9/30 */ public class HeimaService { public void test(){ System.out.println("---目标类逻辑---"); } }
-
代理类:
package cn.itcast.show.inherit; /** * 代理类 * * @Author LK * @Date 2020/9/30 */ public class HeimaProxyService extends HeimaService{ @Override public void test() { super.test(); System.out.println("---代理类逻辑---"); } }
-
运行主类:
package cn.itcast.show; /** * 运行主类 * * @Author LK * @Date 2020/9/30 */ public class TestMain { public static void main(String[] args) { HeimaService heimaService = new HeimaProxyService(); heimaService.test(); } }
-
运行结果:
从上面的结果不难看出,代理类对目标类的逻辑进行了改变,这就称之为代理。
1.3 为什么要代理?
原因一:你可能没有目标类的源码,也就是java文件,这样你如何修改逻辑?
原因二:若是直接在目标类上修改某方法逻辑,而项目中有多处都需要调用该类的该方法,会导致所有调用处的逻辑都发生改变,但假如我只想在某一处调用的地方添加自己的增强逻辑呢?
小结
- 何为代理?代理就是通过代理对象对目标对象的代码逻辑进行增强/改变
- 代理的作用?提高代码扩展性,本质上是设计模式的一中实现方式,对修改关闭,对扩展开放(开闭原则)
2. 静态代理
目标:能够说出静态代理和动态代理的区别,并掌握两种静态代理的代码实现。
2.1 介绍
静态代理和动态代理都是实现代理的两种方式,区别在于:使用静态代理是需要编写代理类的,也就是最终你可以在你项目编译的文件中看到class文件,而动态代理是直接动态生成字节码文件(类加载器加载class文件到JVM中就变成了字节码文件)。
实现静态代理又包含两种方式,分别是继承和聚合。
2.2 继承
上一章节中的样例就是使用的继承的方式,继承方式的核心思想是:使用代理类继承目标类,重写目标类的方法,并在重写的方法中调用目标类的方法,再添加代理的逻辑。
2.3 聚合
通过聚合实现代理,一般有一个前提,就是代理类和目标类都实现同一个接口。我们看下面一个代码样例。
代码样例
-
公用接口
-
package cn.itcast.show.aggregation; /** * 接口 * * @Author LK * @Date 2020/9/30 */ public interface Service { /** * 接口方法 */ public void test(); }
-
目标类
package cn.itcast.show.aggregation; /** * 目标类 * * @Author LK * @Date 2020/9/30 */ public class TargetService implements Service{ public void test() { System.out.println("---目标类逻辑---"); } }
-
代理类
package cn.itcast.show.aggregation; /** * 代码类 * * @Author LK * @Date 2020/9/30 */ public class ProxyService implements Service { // target为目标对象 private Service target; public ProxyService(Service target) { this.target = target; } public void test() { // 调用目标对象的逻辑 target.test(); // 编写代理逻辑 System.out.println("---代理类逻辑---"); } }
-
- 运行主类
package cn.itcast.aggregation; import cn.itcast.show.juhe.ProxyService; import cn.itcast.show.juhe.Service; import cn.itcast.show.juhe.TargetService; /** * 运行主类 * * @Author LK * @Date 2020/9/30 */ public class TestMain { public static void main(String[] args) { // HeimaService heimaService = new HeimaProxyService(); // heimaService.test(); Service service = new ProxyService(new TargetService()); service.test(); } }
-
运行结果
-
聚合的核心思想是:代理类和目标类都实现同一个接口,创建代理对象时,通过构造方法或者set方法将目标对象聚合进去。
小结
- 静态代理和动态代理的区别:有无生成class文件
- 静态代理-继承方式:使用代理类继承目标类,重写目标类的方法,并在重写的方法中调用目标类的方法,再添加代理的逻辑。
- 静态代理-聚合方式:代理类和目标类都实现同一个接口,创建代理对象时,通过构造方法或者set方法将目标对象聚合进去。
3. 动态代理
目标:JDK动态代理和cglib动态代理的区别,以及JDK动态代理的代码实现
3.1 介绍
在前面的章节有讲到,动态代理即动态的生成字节码文件到JVM中,并不会看到class文件。动态代理又分为JDK动态代理和Cglib动态代理,两种的区别在于:JDK动态代理都是对接口进行代理,而Cglib一般是对类进行代理。
值得一提的是,动态代理技术在开源框架中经常被使用到。例如Mybatis、还有Spring源码中也在很多地方使用了动态代理,像@Configuration注解,还有SpringCloud的Feign组件等等。
3.2 JDK动态代理代码
-
JdkService
-
package cn.itcast.show.jdk; /** * TODO * * @Author LK * @Date 2020/10/10 */ public interface JdkService { /** * 接口方法 */ public void test(); }
-
MyInvocationHandler
package cn.itcast.show.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * TODO * * @Author LK * @Date 2020/10/10 */ public class MyInvocationHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-----代理逻辑-----"); return null; } }
-
TestMain
package cn.itcast.show; import cn.itcast.show.jdk.JdkService; import cn.itcast.show.jdk.MyInvocationHandler; import java.lang.reflect.Proxy; /** * 运行主类 * * @Author LK * @Date 2020/9/30 */ public class TestMain { public static void main(String[] args) { // HeimaService heimaService = new HeimaProxyService(); // heimaService.test(); // Service service = new ProxyService(new TargetService()); // service.test(); // 作用:为JdkService接口动态生成一个代理类,并加载到JVM,返回一个JdkService类型的代理对象 JdkService o = (JdkService) Proxy.newProxyInstance(JdkService.class.getClassLoader(), new Class[]{JdkService.class}, new MyInvocationHandler()); // 实际上会执行MyInvocationHandler的invoke方法 o.test(); } }
-
运行效果
4. 模拟原生Mybatis
本章内容为模拟Mybatis的动态代理部分,其他类似于解析xml文件就不模拟了。
4.1 原生Mybatis使用
先来看看原生mybatis的用法,使用步骤如下:
-
第一步:添加依赖
<?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>cn.itcast</groupId> <artifactId>itheima-demo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies> </project>
-
第二步:resources目录下添加mybatis配置文件:mybatis-config.xml
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/extend_db"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- <mappers>--> <!-- <mapper resource="mappers/UserMapper.xml"/>--> <!-- </mappers>--> <mappers> <mapper class="cn.itcast.show.mockMybatis.UserMapper"></mapper> </mappers> </configuration>
-
第三步:准备数据库数据,执行资料中的tb_user.sql文件
-
第四步:编写实体类
-
package cn.itcast.show.mockMybatis; /** * 实体类 * * @Author LK * @Date 2020/11/30 */ public class User { private Long id; private String name; private int age; public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
-
第五步:编写mybatis接口
package cn.itcast.show.mockMybatis; import org.apache.ibatis.annotations.Select; import java.util.List; /** * TODO * * @Author LK * @Date 2020/11/30 */ public interface UserMapper { @Select("select * from tb_user") public List<User> findList(); }
-
- 第六步:测试类
package cn.itcast.show; import cn.itcast.show.mockMybatis.User; import cn.itcast.show.mockMybatis.UserMapper; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.InputStream; import java.util.List; /** * TODO * * @Author LK * @Date 2020/11/30 */ public class MybatisTest { @Test public void testMybatis() throws Exception{ String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = userMapper.findList(); System.out.println("userList = " + userList); } }
-
运行结果:
以上,我们完成了原生mybatis的查询功能,在以下代码这行打断点,debug模式启动
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
debug步骤如下:
点击 Step Into
点击 Step Into
点击Step Over
再点击Step Into
4.2 模拟Mybatis动态代理
-
第一步:编写mybatis的InvocationHandler类
package cn.itcast.show.mockMybatis; import org.apache.ibatis.annotations.Select; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * TODO * * @Author LK * @Date 2020/11/30 */ public class MybatisInvocationHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { List<User> userList = new ArrayList<User>(); Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 使用jdbc模拟,实际上Mybatis最底层仍然是使用jdbc Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/extend_db", "root", "123456"); // 重点是获取sql语句,在userMapper的方法注解上 Select annotation = method.getAnnotation(Select.class); String[] value = annotation.value(); String sqlStr = value[0]; preparedStatement = connection.prepareStatement(sqlStr); resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ User user = new User(); user.setId(resultSet.getLong("id")); user.setName(resultSet.getString("name")); user.setAge(resultSet.getInt("age")); userList.add(user); } } catch (Exception e) { e.printStackTrace(); } finally { if(connection!=null){ connection.close(); } if(preparedStatement!=null){ preparedStatement.close(); } if(resultSet!=null){ resultSet.close(); } } return userList; } }
-
第二步:编写测试类
-
package cn.itcast.show; import cn.itcast.show.mockMybatis.MybatisInvocationHandler; import cn.itcast.show.mockMybatis.User; import cn.itcast.show.mockMybatis.UserMapper; import org.junit.Test; import java.lang.reflect.Proxy; import java.util.List; /** * TODO * * @Author LK * @Date 2020/11/30 */ public class MockMybatisTest { @Test public void testMockMybatis(){ // 得到代理对象 UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new MybatisInvocationHandler()); List<User> userList = userMapper.findList(); System.out.println("userList = " + userList); } }
-
第三步:运行结果:
5. 如何生成Bean到Spring
整合Spring的关键点在于,将mybatis的接口类变成Bean,放到Spring容器当中。我们前面已经学过了好几种方式生成Bean了,我们简单回顾一下:
-
方式一:spring的xml文件中,通过标签
-
方式二:类上加入@Component注解或其子注解(@Service、@Controller、@Configuration等),然后通过@ComponentScan扫描。
-
方式三:已有的Bean中,添加@Bean注解在方法上,把方法的返回值变成一个Bean。
-
方式四:已有的Bean上,使用@Import注解,导入一个ImportSelector的实现类,在重写的selectImports方法中,返回的类全路径,会把返回值中的类变成Bean。
-
方式五:已有的Bean上,使用@Import注解,导入一个ImportBeanDefinitionRegistrar的实现类,在重写的方法注册BD到Spring容器。
但是,无论是以上哪种方式都不可以,为什么呢?因为就把接口放到Spring容器,即使它能够变成一个Bean,但是依然不能创建对象,
因此,重点应该是把JDK动态代理得到的那个对象变成一个Bean,这里需要用到Spring一个知识点:FactoryBean
6. 模拟Mybatis集成Spring
加入spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
编写UserMapperFactoryBean
package cn.itcast.show.mockMybatis;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.Proxy;
/**
* userservice facotryBean
*
* @Author LK
* @Date 2020/12/1
*/
public class UserMapperFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(),
new Class[]{UserMapper.class},
new MybatisInvocationHandler());
return userMapper;
}
// 返回对象类型
public Class<?> getObjectType() {
return UserMapper.class;
}
// 是否单例
public boolean isSingleton() {
return true;
}
}
编写主函数
package cn.itcast.show;
import cn.itcast.show.mockMybatis.UserMapper;
import cn.itcast.show.mockMybatis.UserMapperFactoryBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* TODO
*
* @Author LK
* @Date 2020/11/30
*/
public class SpringMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// 肯定不能在FactoryBean上加@Component注解,因为这种方式非常有局限性,spring怎么知道你的类在哪?
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.setBeanClass(UserMapperFactoryBean.class);
ac.registerBeanDefinition("userMapper", beanDefinition);
// 虽然获取的是userMapperFactoryBean, 但是实际返回值却是该类中getObject返回的对象
UserMapper userMapper = (UserMapper) ac.getBean("userMapper");
System.out.println(userMapper.findList());
}
}
修改配置类
package cn.itcast.show;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* TODO
*
* @Author LK
* @Date 2020/11/30
*/
@Configuration
public class Config {
}
运行结果
[User{id=1, name='张三', age=18}, User{id=2, name='李四', age=19}, User{id=3, name='王五', age=20}]
但是现在有个致命的缺陷,就是虽然把UserMapper变成了一个Bean,但在实际项目中,不可能只有一个UserMapper接口吧?如果有成百上千个Service接口,岂不是要写非常多XXXFactoryBean这样的类?因此我们需要优化一下代码,写一个公共的FactoryBean
创建公共FactoryBean:MybatisSpringFactoryBean
package cn.itcast.show.mockMybatis;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.Proxy;
/**
* 公共的factoryBean
*
* @Author LK
* @Date 2020/12/1
*/
public class MybatisSpringFactoryBean implements FactoryBean {
// 代理的接口
Class mapperInterface;
public MybatisSpringFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Object getObject() throws Exception {
return Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[]{mapperInterface},
new MybatisInvocationHandler());
}
public Class<?> getObjectType() {
return mapperInterface;
}
public boolean isSingleton() {
return false;
}
}
修改主函数
package cn.itcast.show;
import cn.itcast.show.mockMybatis.MybatisSpringFactoryBean;
import cn.itcast.show.mockMybatis.UserMapper;
import cn.itcast.show.mockMybatis.UserMapperFactoryBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* TODO
*
* @Author LK
* @Date 2020/11/30
*/
public class SpringMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// 肯定不能在FactoryBean上加@Component注解,因为这种方式非常有局限性,spring怎么知道你的类在哪?
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.setBeanClass(MybatisSpringFactoryBean.class);
// 添加构造方法的参数
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue("cn.itcast.show.mockMybatis.UserMapper");
ac.registerBeanDefinition("userMapper", beanDefinition);
UserMapper userMapper = (UserMapper) ac.getBean("userMapper");
System.out.println(userMapper.findList());
}
}
现在又有问题, 我的这个代码放在主函数里,太不优雅了,而且实际项目中我们也拿不到ApplicaitonContex,如何解决?Spring提供了一种方式给我们
编写一个ImportBeanDefinitionRegistrar的实现类:MyImportBeanDefinitionRegistrar
package cn.itcast.show.mockMybatisSpring;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* TODO
*
* @Author LK
* @Date 2020/12/1
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.setBeanClass(MybatisSpringFactoryBean.class);
// 添加构造方法的参数
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue("cn.itcast.show.mockMybatis.UserMapper");
registry.registerBeanDefinition("userMapper", beanDefinition);
}
}
自定义注解类:MyMapperScan,并且在上面加入Import注解
package cn.itcast.show.mockMybatisSpring;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {
String[] value();
}
在配置类Config中添加@MyMapperScan注解
package cn.itcast.show;
import cn.itcast.show.mockMybatisSpring.MyMapperScan;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
/**
* TODO
*
* @Author LK
* @Date 2020/11/30
*/
@MyMapperScan("cn.itcast.show.mockMybatis")
@Configuration
public class Config {
}
package cn.itcast.show;
import cn.itcast.show.mockMybatisSpring.MyMapperScan;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
/**
* TODO
*
* @Author LK
* @Date 2020/11/30
*/
@MyMapperScan("cn.itcast.show.mockMybatis")
@Configuration
public class Config {
}
修改main函数
package cn.itcast.show;
import cn.itcast.show.mockMybatisSpring.MybatisSpringFactoryBean;
import cn.itcast.show.mockMybatis.UserMapper;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* TODO
*
* @Author LK
* @Date 2020/11/30
*/
public class SpringMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
UserMapper userMapper = (UserMapper) ac.getBean("userMapper");
System.out.println(userMapper.findList());
}
}
我们之前讲过,Spring启动过程中,如果在已有的Bean上存在@Import注解,导入一个ImportBeanDefinitionRegistrar的实现类,会默认执行重写的方法,也就是registerBeanDefinitions方法。重新执行,依然是可以得到结果:
[User{id=1, name='张三', age=18}, User{id=2, name='李四', age=19}, User{id=3, name='王五', age=20}]
可是代码还不够完美,在MyImportBeanDefinitionRegistrar中是写死代码只注册UserMapper的,我们应该是需要把@MyMapperScan注解中的指定的包下面所有的接口都注册到Spring容器里,继续改造:
MyImportBeanDefinitionRegistrar
package cn.itcast.show.mockMybatisSpring;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
/**
* TODO
*
* @Author LK
* @Date 2020/12/1
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取到注解的属性
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName());
// 获取注解的value属性值
String[] value = (String[]) attributes.get("value");
if (null != value && value.length > 0) {
// 得到包路径
String packagePath = value[0];
System.out.println("packagePath = " + packagePath);
// 自定义扫描器,不能用原本spring的扫描器,因为会按照spring自己的逻辑去扫描,扫描不到
MyClassPathBeanDefinitionScanner scanner = new MyClassPathBeanDefinitionScanner(registry);
// 允许扫描
scanner.addIncludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
// 得到扫描到的beanDefinition包装对象集合
Set<BeanDefinitionHolder> beanDefinitionHolders = scanner.doScan(packagePath);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
// 注册到spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, registry);
}
}else{
System.out.println("未指定扫描包路径");
}
}
}
新增类:MyClassPathBeanDefinitionScanner
package cn.itcast.show.mockMybatisSpring;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import java.util.Set;
/**
* 自定义扫描器
*
* @Author LK
* @Date 2020/12/1
*/
public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
// 扫描
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
// 父类扫描到的beanDefinition,不符合我们要求,我们需要改内容
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
// 原本的className
String beanClassName = beanDefinition.getBeanClassName();
// 设置为FactoryBean的name
beanDefinition.setBeanClassName(MybatisSpringFactoryBean.class.getName());
// 添加构造方法的参数
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
}
return beanDefinitionHolders;
}
@Override
// 只扫描接口
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}