Оригинальный адрес:у-у-у-у. xilidu.com/2018/01/08/…
Как стандарт де-факто для разработки J2ee, Spring представляет собой среду, о которой должен знать каждый разработчик Java. Тем не менее, функции Spring IoC и Aop по-прежнему сложны для понимания младшими разработчиками Java. Поэтому я хочу написать серию статей, чтобы объяснить вам эти особенности. Это позволяет глубже понять структуру Spring.
Прочитав эту статью, вы будете знать:
- Что такое внедрение зависимостей и инверсия управления
- Для чего нужны IOC?
- Как реализован Spring Ioc
- Разработайте простую структуру Ioc в соответствии с идеями Spring.
Что такое ИОК?
Объяснение Википедии:
Инверсия управления (сокращенно IoC) — это принцип проектирования в объектно-ориентированном программировании, который можно использовать для уменьшения связи между компьютерными кодами. Наиболее распространенный способ называется внедрение зависимостей (DI). Посредством инверсии управления при создании объекта внешний объект, управляющий всеми объектами в системе, передает ему ссылку на объект, от которого он зависит. Можно также сказать, что зависимости внедряются в объекты.
Для чего нужны IOC?
Прочитав приведенное выше объяснение, вы не должны понимать, что такое IoC, потому что впервые видите приведенные выше слова и чувствуете себя в тумане.
Однако из приведенного выше описания мы можем примерно понять, что целью использования IoC является развязка. Другими словами, IoC — это метод развязки.
Мы знаем, что Java — объектно-ориентированный язык, в Java все является объектом, и наша программа состоит из нескольких объектов. Когда наш проект становится все больше и больше, и все больше разработчиков сотрудничают, у нас будет все больше и больше классов, а ссылки между классами будут расти в геометрической прогрессии. Как показано ниже:
Такой проект просто катастрофа, если ввести IoC framework. Платформа должна поддерживать жизненный цикл классов и ссылок между классами. Наша система будет выглядеть так:
На этот раз мы обнаружили, что связь между нашими классами поддерживается инфраструктурой IoC, и класс внедряется в требуемый класс. Другими словами, пользователь класса отвечает только за использование, а не за обслуживание. Пусть профессиональные вещи делают профессиональные фреймворки. Значительно снизить сложность разработки.
Используйте аналогию, чтобы понять проблему. Ioc framework — это жилищное агентство в нашей жизни.Во-первых, агентство соберет предложения на рынке и установит контакт с арендодателями каждого объявления. Когда нам нужно арендовать дом, нам не нужно искать всевозможную информацию об аренде. Мы напрямую связываемся с агентом по недвижимости, и агент предоставит соответствующую информацию о жилье в соответствии с вашими потребностями. Это значительно повышает эффективность сдачи в аренду и сокращает количество коммуникаций между вами и различными арендодателями.
Как реализован IoC Spring
Самый прямой способ понять структуру Spring — прочитать исходный код Spring. Но уровень абстракции кода Spring очень высок, и детали обработки очень высоки. Не слишком легко понять для большинства людей. После того, как я прочитал исходный код Spirng, я сделал резюме, основанное на моем понимании, Spirng IoC в основном состоит из следующих шагов.
1. 初始化 IoC 容器。
2. 读取配置文件。
3. 将配置文件转换为容器识别对的数据结构(这个数据结构在Spring中叫做 BeanDefinition)
4. 利用数据结构依次实例化相应的对象
5. 注入对象之间的依赖关系
Реализуйте инфраструктуру IoC самостоятельно
Для удобства мы ссылаемся на реализацию IoC Spirng и удаляем всю логику, не связанную с основным принципом. Минималистский фреймворк для реализации IoC. Проект использует json в качестве файла конфигурации. Используйте maven для управления зависимостями пакетов jar.
В этой структуре наши объекты являются одиночками и не поддерживают множественные области видимости Spirng. Фреймворк реализован с использованием cglib и отражения Java. Я также использую ломбок в проекте для упрощения кода.
Теперь давайте напишем фреймворк IoC.
Сначала давайте посмотрим на базовую структуру этого фреймворка:
Глядя на этот фреймворк с точки зрения макросов, он содержит 3 пакета, а структура данных нашего фреймворка определяется в bean-компоненте package. core — это основная логика нашего фреймворка. utils — это некоторые общие служебные классы. Далее мы объясним один за другим:
1. Бин определяет структуру данных фреймворка
BeanDefinition
является основной структурой данных нашего проекта. Используется для описания объектов, которыми нам нужно управлять с помощью инфраструктуры IoC.
@Data
@ToString
public class BeanDefinition {
private String name;
private String className;
private String interfaceName;
private List<ConstructorArg> constructorArgs;
private List<PropertyArg> propertyArgs;
}
Содержит имя объекта и имя класса. Если это реализация интерфейса, существует также интерфейс, реализованный объектом. и список параметров, передаваемых конструкторуconstructorArgs
и список параметров, которые необходимо ввестиpropertyArgs
.
2. Взгляните на объекты в нашем пакете классов инструментов:
ClassUtils
Код, отвечающий за обработку загрузки классов Java, выглядит следующим образом:
public class ClassUtils {
public static ClassLoader getDefultClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
public static Class loadClass(String className){
try {
return getDefultClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
Мы написали только один метод, который должен получить класс объекта через параметр className.
BeanUtils
Отвечает за обработку экземпляров объектов, здесь мы используем инструментарий cglib, код выглядит следующим образом:
public class BeanUtils {
public static <T> T instanceByCglib(Class<T> clz,Constructor ctr,Object[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clz);
enhancer.setCallback(NoOp.INSTANCE);
if(ctr == null){
return (T) enhancer.create();
}else {
return (T) enhancer.create(ctr.getParameterTypes(),args);
}
}
}
ReflectionUtils
Внедрение зависимостей объектов в основном выполняется с помощью принципа отражения Java:
public class ReflectionUtils {
public static void injectField(Field field,Object obj,Object value) throws IllegalAccessException {
if(field != null) {
field.setAccessible(true);
field.set(obj, value);
}
}
}
injectField(Field field,Object obj,Object value)
Функция этого метода состоит в том, чтобы установить значение поля obj.
JsonUtils
Роль состоит в том, чтобы проанализировать наш файл конфигурации json. Код относительно длинный и имеет мало общего с нашим принципом IoC Заинтересованные студенты могут загрузить код с github, чтобы увидеть его.
С помощью этих удобных инструментов мы можем приступить к завершению основного кода инфраструктуры Ioc.
3. Основная логика
Моя структура IoC в настоящее время поддерживает только одну инъекцию ByName. Итак, наша BeanFactory имеет только один метод:
public interface BeanFactory {
Object getBean(String name) throws Exception;
}
Затем реализуем этот метод:
public class BeanFactoryImpl implements BeanFactory{
private static final ConcurrentHashMap<String,Object> beanMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String,BeanDefinition> beanDefineMap= new ConcurrentHashMap<>();
private static final Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<>());
@Override
public Object getBean(String name) throws Exception {
//查找对象是否已经实例化过
Object bean = beanMap.get(name);
if(bean != null){
return bean;
}
//如果没有实例化,那就需要调用createBean来创建对象
bean = createBean(beanDefineMap.get(name));
if(bean != null) {
//对象创建成功以后,注入对象需要的参数
populatebean(bean);
//再把对象存入Map中方便下次使用。
beanMap.put(name,bean;
}
//结束返回
return bean;
}
protected void registerBean(String name, BeanDefinition bd){
beanDefineMap.put(name,bd);
beanNameSet.add(name);
}
private Object createBean(BeanDefinition beanDefinition) throws Exception {
String beanName = beanDefinition.getClassName();
Class clz = ClassUtils.loadClass(beanName);
if(clz == null) {
throw new Exception("can not find bean by beanName");
}
List<ConstructorArg> constructorArgs = beanDefinition.getConstructorArgs();
if(constructorArgs != null && !constructorArgs.isEmpty()){
List<Object> objects = new ArrayList<>();
for (ConstructorArg constructorArg : constructorArgs) {
objects.add(getBean(constructorArg.getRef()));
}
return BeanUtils.instanceByCglib(clz,clz.getConstructor(),objects.toArray());
}else {
return BeanUtils.instanceByCglib(clz,null,null);
}
}
private void populatebean(Object bean) throws Exception {
Field[] fields = bean.getClass().getSuperclass().getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
String beanName = field.getName();
beanName = StringUtils.uncapitalize(beanName);
if (beanNameSet.contains(field.getName())) {
Object fieldBean = getBean(beanName);
if (fieldBean != null) {
ReflectionUtils.injectField(field,bean,fieldBean);
}
}
}
}
}
}
Сначала мы видим в реализации BeanFactory. У нас есть два HashMap, beanMap и beanDefineMap. beanDefineMap хранит сопоставление между именем объекта и структурой данных, соответствующей объекту. beanMap используется для хранения beanName и объекта после создания экземпляра.
Когда контейнер инициализируется, он вызоветBeanFactoryImpl.registerBean
метод. Сохраните структуру данных BeanDefination объекта.
Когда мы вызываем метод getBean(). Сначала он перейдет к beanMap, чтобы узнать, существует ли экземпляр объекта. Если нет, он перейдет к beanDefineMap, чтобы найти BeanDefination, соответствующий этому объекту. Затем используйте DeanDefination для создания экземпляра объекта.
После того, как объект успешно создан, нам также необходимо ввести соответствующие параметры, вызовpopulatebean()
Сюда. В методе populateBean будет сканироваться поле в объекте.Если поле в объекте является объектом, управляемым нашим контейнером IoC, он вызовет тот, который мы реализовали выше.ReflectionUtils.injectField
ввести объект.
Когда все готово, наш объект завершает весь процесс IoC. Наконец, этот объект помещается в beanMap, что удобно для следующего использования.
Таким образом, мы можем знать, что BeanFactory — это место, где объекты управляются и генерируются.
4. Контейнеры
Наш так называемый контейнер является расширением BeanFactory, которое отвечает за управление BeanFactory. Наша структура IoC использует Json в качестве файла конфигурации, поэтому наш контейнер называется JsonApplicationContext. Конечно, если вы хотите реализовать XML в качестве контейнера для файлов конфигурации, вы можете сами написать XmlApplicationContext.Если используется контейнер на основе аннотаций, его можно назвать AnnotationApplicationContext. Эти реализации оставлены для завершения каждому.
Давайте посмотрим на код для ApplicationContext:
public class JsonApplicationContext extends BeanFactoryImpl{
private String fileName;
public JsonApplicationContext(String fileName) {
this.fileName = fileName;
}
public void init(){
loadFile();
}
private void loadFile(){
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
List<BeanDefinition> beanDefinitions = JsonUtils.readValue(is,new TypeReference<List<BeanDefinition>>(){});
if(beanDefinitions != null && !beanDefinitions.isEmpty()) {
for (BeanDefinition beanDefinition : beanDefinitions) {
registerBean(beanDefinition.getName(), beanDefinition);
}
}
}
}
Роль этого контейнера заключается в чтении файлов конфигурации. Преобразуйте файл конфигурации во что-то, что может понять контейнер.BeanDefination
. затем используйтеregisterBean
метод. Зарегистрируйте этот объект.
На этом простая версия инфраструктуры IoC завершена.
5. Использование фреймворка
Давайте напишем тестовый класс, чтобы увидеть, как используется наш фреймворк:
Сначала у нас есть три объекта
public class Hand {
public void waveHand(){
System.out.println("挥一挥手");
}
}
public class Mouth {
public void speak(){
System.out.println("say hello world");
}
}
public class Robot {
//需要注入 hand 和 mouth
private Hand hand;
private Mouth mouth;
public void show(){
hand.waveHand();
mouth.speak();
}
}
Нам нужно ввести руку и рот для нашего робота.
Конфигурационный файл:
[
{
"name":"robot",
"className":"com.xilidou.framework.ioc.entity.Robot"
},
{
"name":"hand",
"className":"com.xilidou.framework.ioc.entity.Hand"
},
{
"name":"mouth",
"className":"com.xilidou.framework.ioc.entity.Mouth"
}
]
На этот раз напишите тестовый класс:
public class Test {
public static void main(String[] args) throws Exception {
JsonApplicationContext applicationContext = new JsonApplicationContext("application.json");
applicationContext.init();
Robot aiRobot = (Robot) applicationContext.getBean("robot");
aiRobot.show();
}
}
Вывод после запуска:
挥一挥手
say hello world
Process finished with exit code 0
Вы можете видеть, что мы успешно внедрили руку и рот в мой aiRobot.
На данный момент наша структура Ioc разработана.
Суммировать
После прочтения этой статьи я полагаю, что вы реализовали простую структуру IoC.
Хотя чтение исходного кода является окончательным средством понимания фреймворка. Однако, как производственная среда, среда Spring должна быть очень абстрактной и иметь дело с большим количеством деталей, чтобы обеспечить универсальность и стабильность. Так что исходный код Spring по-прежнему довольно сложно читать. Надеюсь, эта статья поможет понять реализацию Spring Ioc.
Следующей статьей должна быть "Рамка от руки - Реализация АОП".
адрес github: https://github.com/diaozxin007/xilidou-framework