手写sping框架核心源码之IOC
1.使用Spring框架
2.使用反射机制
Ioc控制反转思想Inverse of Controller创建对象的权限,Java程序员需要用到的对象不再由程序员自己创建,而是交给IOC容器创建
模拟IOC容器的使用
1.使用pom.xml文件信息
<dependencies>
<!-- 引入 Servlet 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 导入Spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
<!-- 设置 Maven 的JDK版本,默认是5,需要手动改到8 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
2.创建一个servlet文件
/**
* 测试mvc的模式的规范的返回值的演示
* @author liugang
* @version 1.0
* @date 2021/6/21 14:54
*/
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
private HelloService helloService = new HelloServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello spring:"+helloService.findAll());
}
}
3.部署到tomcat中去
4.servlet,service,dao 当我们需求发生变更的时候,可能需要频繁的修改Java代码,效率很低
静态工厂模式
public class BeanFactory {
public static HelloDao getDao(){
return new HelloDaoImpl();
}
}
private HelloDao helloDao = BeanFactory.getDao();
上述的方式并不能解决我们的问题,需求发生改变的时候,任然需要去修改代码,怎么做到不改Java代码,就可以实现类的切换的呢?
外部配置文件的方式,Java程序只需要读取配置文件即可,xml,yaml,properties,json
1.定义外部配置文件
helloDao=com.southwind.dao.impl.HelloDaoImpl
2Java程序读取这个配置文件
/**
* 创建一个静态工厂类
* 对于我们的程序来说我们常用的把一些变化比较多的东西我们经常希望可以放到
* 一些配置文件中,这样我们来读取话就可以更好的降低耦合的可能
* @author liugang
* @version 1.0
* @date 2021/6/21 15:22
*/
public class BeanFactory {
//配置其对应的映射文件的内容
private static Properties properties;
//定义一个缓存用来存放我们的实例化对象
private static Map<String,Object> cache = new HashMap<>();
static {
properties = new Properties();
try {
properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
/*通过工厂模式来生产对象*/
public static Object getDao(String beanName){
//先判断一下缓存中是否存在bean
if (!cache.containsKey(beanName)){
//此时我们用的是锁的机制来实现对相关的容器的控制
synchronized (BeanFactory.class){
//先判断一下缓存中是否存在bean 这里是没有bean,我们就把其对应的bean存入到对应的缓存中去
if (!cache.containsKey(beanName)){
try {
String value = properties.getProperty("helloDao");
//利用反射机制创建对象
Class clazz = Class.forName(value);
Object object = clazz.getConstructor().newInstance(null);
cache.put(beanName,object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return cache.get(beanName);
}
}
3.修改service
private HelloDao helloDao = (HelloDao) BeanFactory.getDao();
这样的话程序就从那种紧耦合/强依赖变成了松耦合/弱依赖,编译之后任然可以修改,让程序有了很好的拓展性
自己放弃了创建对象的权限,将创建对象的权限交给了BeanFactory,这种将控制权交给别人的思想,就是控制反转
Spring Ioc的使用
xml和注解,xml方式已经被淘汰了,目前主流的就是基于注解的方式,springboot也是基于注解的方式
/**
* 将实体类注入到容器中
* @author liugang
* @version 1.0
* @date 2021/6/22 17:04
*/
@Data
@Component
public class Account {
@Value("1")
private Integer id;
@Value("liugang")
private String name;
@Value("20")
private int age;
}
/**
* @author liugang
* @version 1.0
* @date 2021/6/22 17:27
*/
public class SpringTest {
public static void main(String[] args) {
//获取ioc容器 这里是对注解的方法的控制的形式,对相关的包下面的容器进行获取
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.zzuli.spring.entity");
//Spring 容器中所有 JavaBean 的名称,返回类型是一个字符串数组。
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
//获取Spring容器中bean的数量
System.out.println(applicationContext.getBeanDefinitionCount());
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
//获取这样的实例化的bean对象
System.out.println(applicationContext.getBean(beanDefinitionName));
}
}
}
IOC基于注解的执行原理
手写代码的思路:
1.自定义一个MyAnnotationConfigApplicationContext,构造器中传入一个要扫描的包
2.获取这个包下的所以类
3.遍历这些类,找出了添加了@Component注解的类,获取它的对应的class和对应的beanName,封装成一个beanDefinition,存入Set集合,这个机会就是IOC自动装载的原材料
4.遍历Set集合,通过反射机制创建对象,同时检测属性上有没有添加@Value对象,如果有还需要给属性赋值,再讲这些动态创建得到的对象以k-v的形式存入缓存中
5.提供getBean等方法,通过beanName取出对应的bean即可
代码实现
各个注解类和容器封装的实体类的对象集合
/**
* 容器的实体类的对象集合
* @author liugang
* @version 1.0
* @date 2021/6/23 9:36
*/
@Data
@AllArgsConstructor
public class BeanDefinition {
private String beanName;
private Class beanClass;
}
/**
* 这里是自动装载的注解
* @author liugang
* @version 1.0
* @date 2021/6/24 14:36
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
/**
* 自定义容器注解
* @author liugang
* @version 1.0
* @date 2021/6/23 20:41
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default ""; //default "" 表示其对应的默认值是空,这个时候component的注解可以不添加值
}
/**
* 按照名字进行装载的注解
* @author liugang
* @version 1.0
* @date 2021/6/24 14:41
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Qualifier {
String value();
}
/**
* @author liugang
* @version 1.0
* @date 2021/6/23 22:38
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Value {
String value(); //这里没有添加default默认值让我们这个值可以赋值 当我们使用这个注解的时候要求我们手动去赋值
}
核心类书写
/**
* @author liugang
* @version 1.0
* @date 2021/6/23 20:10
*/
public class MyAnnotationConfigApplicationContext {
//定义一个缓存的容器
private Map<String,Object> ioc = new HashMap<>();
/*构造方法,用来封装我们想要的数据的*/
public MyAnnotationConfigApplicationContext(String pack) {
//遍历包,找到目标类(原材料)
Set<BeanDefinition> beanDefinitions = findBeanDefinitions(pack);
/*Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
while (iterator.hasNext()){
BeanDefinition beanDefinition = iterator.next();
System.out.println(beanDefinition);
}*/
//根据原材料来创建bean
createObject(beanDefinitions);
//自动装载功能
autowireObject(beanDefinitions);
}
/*自动装载功能*/
public void autowireObject(Set<BeanDefinition> beanDefinitions){
Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
while (iterator.hasNext()){
BeanDefinition beanDefinition = iterator.next();
//获取其对应的字节码文件
Class aClass = beanDefinition.getBeanClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
//遍历其对应的每个元素信息
Autowired annotation = declaredField.getAnnotation(Autowired.class);
if (annotation!=null){
//获取这个qualifier注解信息
Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
if (qualifier!=null){
//byName
try {
String beanName = qualifier.value();
Object bean = getBean(beanName); //account对应的Order对象
String fieldName = declaredField.getName();
String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
Method method = aClass.getMethod(methodName, declaredField.getType());
Object object = getBean(beanDefinition.getBeanName()); //account对象
method.invoke(object, bean);
} catch (Exception e) {
e.printStackTrace();
}
} else {
//byType
}
}
}
}
}
/*获取ioc容器名称的方法*/
public Object getBean(String beanName){
return ioc.get(beanName);
}
/*根据原材料来创建bean*/
public void createObject(Set<BeanDefinition> beanDefinitions){
Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
while (iterator.hasNext()){
BeanDefinition beanDefinition = iterator.next();
//获取其对应的原材料的class属性以及相关的对象
Class aClass = beanDefinition.getBeanClass();
String beanName = beanDefinition.getBeanName();
try {
Object object = aClass.getConstructor().newInstance();
//将原材料放入容器之前我们要先完成其对应的赋值操作
//获取每个变量元素
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
Value valueAnnotation = field.getAnnotation(Value.class);
if (valueAnnotation!=null){
//获取对应的值
String value = valueAnnotation.value();
String fieldName = field.getName();
String methodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
Method method = aClass.getMethod(methodName, field.getType());
//因为数据格式的类型不同,所以会出现很多次错误的地方,在这里可以完成相关的类型转换
Object val = null;
switch (field.getType().getName()){
case "java.lang.Integer":
val = Integer.parseInt(value); break;
case "java.lang.String":
val = value; break;
case "java.lang.Float":
val = Float.parseFloat(value); break;
}
method.invoke(object,val);
}
}
ioc.put(beanName,object); //这里是将其对应的原材料对象存入到ioc容器中去
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*获取原材料*/
private Set<BeanDefinition> findBeanDefinitions(String pack) {
//1.遍历包下的所有类
Set<Class<?>> classes = MyTools.getClasses(pack);
//定义一个装BeanDefinition的集合
Set<BeanDefinition> beanDefinitions =new HashSet<>();
Iterator<Class<?>> iterator = classes.iterator();
while (iterator.hasNext()){
//2.遍历这些类,找到添加了注解的类
Class<?> aClass = iterator.next();
Component componentAnno = aClass.getAnnotation(Component.class);
if (componentAnno!=null){
//获取Component注解的值
String beanName = componentAnno.value();
if ("".equals(beanName)){
//如果其注解的值是空的,那么就获取类名的小写
String className = aClass.getName().replaceAll(aClass.getPackage().getName() + ".", "");
beanName = className.substring(0,1).toLowerCase()+className.substring(1);
}
//3.将这些封装到beanDefinitions,存入到集合中
beanDefinitions.add(new BeanDefinition(beanName,aClass));
}
}
return beanDefinitions;
}
}
测试类编写和演示效果
public class MyspringTest {
public static void main(String[] args) {
//一启动的时候就可以看到其对应的构造函数就实现了访问
MyAnnotationConfigApplicationContext myAnnotationConfigApplicationContext = new MyAnnotationConfigApplicationContext("com.zzuli.myspring.entity");
//获取对应的bean
System.out.println(myAnnotationConfigApplicationContext.getBean("account"));
System.out.println(myAnnotationConfigApplicationContext.getBean("myOrder"));
}
}