IOC 实现原理
开发工作多年,spring源码没有特意去看过。理解实现原理,不如自己实现简易版的进一步理解IOC到底是怎样实现。下面实现一个最简单的ioc容器
模拟IOC容器获取bean
- 注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 注入注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface AutoInject {
//注入bean的名称
String value() default "";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 注册bean到IOC容器
*/
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
//存入到IOC容器,bean的名称
String value() default "";
}
- BeanFactory
package com.lg.ioc.core;
import com.lg.ioc.core.annotation.AutoInject;
import com.lg.ioc.core.annotation.MyBean;
import com.lg.ioc.core.utils.ClassUtils;
import com.sun.deploy.util.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* bean工厂
* 1.扫描包到IOC容器(注意IOC容器是存储源对象代理对象)
* 2.给bean注入依赖对象(依赖对象也是代理对象)
* 3.获取bean(获取的也是代理对象)
*/
public class BeanFactory {
//基础扫描包路径
private String basePackage;
//上下文对象
private Context context = new Context();
//工厂构造函数
public BeanFactory(String basePackage) {
this.basePackage = basePackage;
init();
}
//工厂初始化
private void init() {
//1.扫描包到IOC容器(注意IOC容器是存储源对象代理对象)
List beanInfoList = scanPackageAndLoadBeans();
//2.给bean注入依赖对象(依赖对象也是代理对象)
injectBeans(beanInfoList);
}
private void injectBeans(List beanInfoList) {
//遍历每一个bean
for (BeanInfo beanInfo : beanInfoList) {
try {
//获取IOC的bean类型
Class beanClass = beanInfo.getClz();
//获取IOC的bean实例对象
Object bean = beanInfo.getBean();
//查询当前bean所有字段
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
if(declaredField.isAnnotationPresent(AutoInject.class)) {
//获取@AutoInject注解信息
AutoInject autoInjectAnnotation = declaredField.getAnnotation(AutoInject.class);
//获取注入bean名称
String injectBeanName = autoInjectAnnotation.value();
//获取注入bean类型
Class injectBeanType = declaredField.getType();
//从IOC容器查找bean对象
Object proxyBean;
if(!"".equals(injectBeanName)) {
//根据名称,获取bean
proxyBean = context.getBean(injectBeanName);
}else {
//根据类型,获取bean
proxyBean = context.getBean(injectBeanType);
}
//设置当前字段可访问
declaredField.setAccessible(true);
//将从IOC获取的bean,注入到当前字段
declaredField.set(bean, proxyBean);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private List scanPackageAndLoadBeans() {
List myBeanList = new ArrayList<>();
//获取包路径下的所有类
Set classNames = ClassUtils.getClassName(basePackage, true);
for (String className : classNames) {
try {
//获取反射
//Class extends String> aClass = className.getClass();
Class aClass = Class.forName(className);
//判断是否存在MyBean注解
if (aClass.isAnnotationPresent(MyBean.class)) {
//获取注解信息
MyBean myBeanAnnotation = (MyBean)aClass.getAnnotation(MyBean.class);
//获取注解value
String beanName = myBeanAnnotation.value();
//获取当前类实现的接口
Class[] interfaces = aClass.getInterfaces();
//记录是否可以使用jdk动态代理(有接口方可进入jdk动态代理,创建代理对象)
boolean canJdkProxyBean = interfaces != null && interfaces.length > 0;
//获取bean类型,存入IOC容器要用
Class beanType = getBeanType(aClass, canJdkProxyBean);
//实例对象
Object bean = aClass.newInstance();//原始对象实例对象
Object iocBean;//存入IOC容器 实例对象
if(canJdkProxyBean) {
//如果可jdk动态代理,就创建动态代理对象
iocBean = this.createProxyBean(bean);
}else {
iocBean = bean;
}
//把解析出实例对象bean,存入到IOC容器
if(!"".equals(className)) {
//按照名称 存入到IOC容器
context.putBean(beanName, iocBean);
}
//存入容器时,根据类型一定要存入的,根据名称存入是依赖传参
context.putBean(beanType, iocBean);
//组装beanInfo,暂存bean信息
BeanInfo beanInfo = new BeanInfo();
beanInfo.setClz(beanType);
beanInfo.setBeanName(beanName);
beanInfo.setBeanType(beanType);
beanInfo.setBean(bean);
beanInfo.setProxyBean(iocBean);
myBeanList.add(beanInfo);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return myBeanList;
}
private Object createProxyBean(Object bean) {
InvocationHandler invocationHandler = new BeanProxy(bean);
Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(), invocationHandler);
return proxyBean;
}
private Class getBeanType(Class aClass, boolean canJdkProxyBean) {
Class beanType;
if(canJdkProxyBean) {
//如果是实现接口的类,可以使用jdk动态代理类
beanType = aClass.getInterfaces()[0];
} else {
beanType = aClass;
}
return beanType;
}
//根据类型,获取bean
public T getBean(Class clz) {
return (T) context.getBean(clz);
}
//根据名称,获取bean
public T getBean(String beanName) {
return (T) context.getBean(beanName);
}
}
- bean信息
/**
* bean类型信息
*/
public class BeanInfo {
//bean类型
private Class clz;
//存入容器IOC的bean名称
private String beanName;
//存入容器IOC的bean类型
private Class beanType;
//存入容器IOC的bean实例对象
private Object bean;
//存入容器IOC的bean代理对象
private Object proxyBean;
public Class getClz() {
return clz;
}
public void setClz(Class clz) {
this.clz = clz;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public Class getBeanType() {
return beanType;
}
public void setBeanType(Class beanType) {
this.beanType = beanType;
}
public Object getBean() {
return bean;
}
public void setBean(Object bean) {
this.bean = bean;
}
public Object getProxyBean() {
return proxyBean;
}
public void setProxyBean(Object proxyBean) {
this.proxyBean = proxyBean;
}
}
- context 上下文对象,用于保存应用运行时的信息 类似applicationContext
import java.util.HashMap;
import java.util.Map;
/**
*上下文对象,用于保存应用运行时的信息 类似applicationContext
* 1.map结构IOC容器,存入的bean
* 2.存入bean
* 3.取出bean
*/
public class Context {
//相当于IOC容器根据name存储
private Map containerBeanName = new HashMap<>();
//相当于IOC容器根据name存储
private Map containerBeanClass = new HashMap<>();
public Map getContainerBeanName() {
return containerBeanName;
}
public Object getBean(String beanName) {
return containerBeanName.get(beanName);
}
public Object getBean(Class clz) {
return containerBeanClass.get(clz);
}
public void putBean(String beanName, Object proxyBean) {
containerBeanName.put(beanName, proxyBean);
}
public void putBean(Class clz, Object proxyBean) {
containerBeanClass.put(clz, proxyBean);
}
}
- 工具类:将包路径下的类解析出来
package com.lg.ioc.core.utils;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* 类工具
*/
public class ClassUtils {
/**
* 获取某包下所有类
*
* @param packageName 包名
* @param isRecursion 是否遍历子包
* @return 类的完整名称
*/
public static Set getClassName(String packageName, boolean isRecursion) {
Set classNames = new HashSet<>();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String packagePath = packageName.replace(".", "/");
URL url = loader.getResource(packagePath);
if (url != null) {
String protocol = url.getProtocol();
if (protocol.equals("file")) {
String filePath = null;
try {
filePath = URLDecoder.decode(url.getPath(), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (filePath != null) {
classNames = getClassNameFromDir(filePath, packageName, isRecursion);
}
}else if (protocol.equals("jar")) {
JarFile jarFile = null;
try {
jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
} catch (Exception e) {
e.printStackTrace();
}
if (jarFile != null) {
classNames = getClassNameFromJar(jarFile.entries(), packageName, isRecursion);
}
}
} else {
/*从所有的jar包中查找包名*/
classNames = getClassNameFromJars(((URLClassLoader) loader).getURLs(), packageName, isRecursion);
}
return classNames;
}
/**
* 从项目文件获取某包下有类
*
* @param filePath 文件路径
* @param isRecursion 是否遍历子包
* @return 类的完整名称
*/
private static Set getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
Set className = new HashSet<>();
File file = new File(filePath);
File[] files = file.listFiles();
for (File childFile : files) {
if (childFile.isDirectory()) {
if (isRecursion) {
className.addAll(getClassNameFromDir(childFile.getPath(), packageName + "." + childFile.getName(), isRecursion));
}
} else {
String fileName = childFile.getName();
if (fileName.endsWith(".class") && !fileName.contains("$")) {
className.add(packageName + "." + fileName.replace(".class", ""));
}
}
}
return className;
}
/**
* @param jarEntries
* @param packageName
* @param isRecursion
* @return
*/
private static Set getClassNameFromJar(Enumeration jarEntries, String packageName,
boolean isRecursion) {
Set classNames = new HashSet();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
if (!jarEntry.isDirectory()) {
/*
* 这里是为了方便,先把"/" 转成 "." 再判".class" 的做法可能会有bug
* (FIXME: 先把"/" 转成 "." 再判".class" 的做法可能会有bug)
*/
String entryName = jarEntry.getName().replace("/", ".");
if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) {
entryName = entryName.replace(".class", "");
if (isRecursion) {
classNames.add(entryName);
} else if (!entryName.replace(packageName + ".", "").contains(".")) {
classNames.add(entryName);
}
}
}
}
return classNames;
}
/**
* 从所有jar中搜索该包,并获取该包下所有类
*
* @param urls URL集合
* @param packageName 包名
* @param isRecursion 是否递归遍历子包
* @return 类的完整名称
*/
private static Set getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
Set classNames = new HashSet<>();
for (int i = 0; i < urls.length; i++) {
String classPath = urls[i].getPath();
//不必搜索classes文件夹?
if (classPath.endsWith("classes/")) {
continue;
}
JarFile jarFile = null;
try {
jarFile = new JarFile(classPath.substring(classPath.indexOf("/")));
} catch (IOException e) {
e.printStackTrace();
}
if (jarFile != null) {
classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion));
}
}
return classNames;
}
}
- BeanProxy: 只有实现接口的类,可以jdk动态代理,代理目的为了增强功能
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
代理对象,jdk动态代理
*/
public class BeanProxy implements InvocationHandler {
//被代理的对象
private Object bean;
//构造函数,初始化被代理的对象
public BeanProxy(Object bean) {
this.bean = bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理对象执行方法前..........:" + method.getName());
Object result = method.invoke(bean, args);
System.out.println("代理对象执行方法后............:" + method.getName());
return result;
}
}
- 验证测试
- controller
/**
* 模拟controller
*/
@MyBean("userController")
public class UserController {
@AutoInject("userService")
private IUserService userService;
public String getUser(Long id) {
return userService.getUser(id);
}
}
- service
public interface IUserService {
String getUser(Long id);
}
@MyBean("userService")
public class UserServiceImpl implements IUserService{
@Override
public String getUser(Long id) {
return "当前用户id:" + id;
}
}
- main
public class Main {
public static void main(String[] args) {
//定义扫描包路径
String basePackage = "com.lg.ioc.example";
//初始化bean工厂
BeanFactory beanFactory = new BeanFactory(basePackage);
//获取bean
UserController userController = beanFactory.getBean(UserController.class);
//调用bean中方法
String user = userController.getUser(1l);
System.out.println(user);
}
}
- 结果打印
代理对象执行方法前..........:getUser
代理对象执行方法后............:getUser
当前用户id:1
总结
- 注解@Target和Retention,使用作用
注解@Target和@Retention可以用来修饰注解,是注解的注解,称为元注解。
@Target : Target翻译中文为目标,即该注解可以声明在哪些目标元素之前,也可理解为注释类型的程序元素的种类。
ElementType.PACKAGE:该注解只能声明在一个包名前。
ElementType.ANNOTATION_TYPE:该注解只能声明在一个注解类型前。
ElementType.TYPE:该注解只能声明在一个类前。
ElementType.CONSTRUCTOR:该注解只能声明在一个类的构造方法前。
ElementType.LOCAL_VARIABLE:该注解只能声明在一个局部变量前。
ElementType.METHOD:该注解只能声明在一个类的方法前。
ElementType.PARAMETER:该注解只能声明在一个方法参数前。
ElementType.FIELD:该注解只能声明在一个类的字段前。
@Retention :Retention 翻译成中文为保留,可以理解为如何保留,即告诉编译程序如何处理,也可理解为注解类的生命周期。
RetentionPolicy.SOURCE : 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
RetentionPolicy.CLASS : 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
RetentionPolicy.RUNTIME : 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
那怎么来选择合适的注解生命周期呢?
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。
工厂模式,代理模式
jdk动态代理原理,什么情况才可以使用
它是在运行时生成的一种类,在生成它时,必须提供一组 interfaces 给它,然后该类就会实现这些 interface。动态代理类就是 Proxy,它不会替你干任何事,在生成它的时候,也必须提供一个 handler,由它接管实际的工作。
jdk动态代理原理名称注入和类型注入
存入IOC容器时,存储两份,一份名称一份类型,获取bean时,两者选其一