关于ioc:初识Spring-IoC

零、前言从第一天开始学习SpringBoot,到当初曾经有两年了,两年内我的搬砖能力一直加强,但实践层面依然是零根底小白。因而,突破瓶颈的办法,就是像学长们一样,多读书、多看理论知识,而后利用实际。 所以我开始学习Spring的两大个性之一——管制反转IoC。 在此之前,咱们须要理解什么是依赖关系。如果A的胜利运行必须须要调用B,此时就能够称为B是A的依赖。举个例子,Controller要想调用Service的办法,就必须有一个能够操作的Service对象的援用(指针),此时这个Controller就依赖这个Service。 一、惯例状况下,依赖关系的对象是怎么实例化的最传统的形式就是:间接new对象,用谁就new谁。例如:如果想实现一个学生治理的性能,须要StudentController(控制器)、StudentService(服务)以及StudentRepository(仓库),而学生和班级是ManyToOne,因而可能会调用KlassService(班级服务)和KlassRepository(班级仓库)而在实现班级治理时,也是相似的依赖关系。如图所示: 如果学生Service想查看学生信息,就要: // 实例化学生仓库private StudentRepository studentRepository = new StudentRepository();// 实例化班级服务和班级仓库,从而实现关联查问private KlassService klassService = new KlassService();private KlassRepository klassRepository = KlassRepository();并且,如果班级Service想查看班级信息,就要: // 实例化班级仓库private KlassRepository klassRepository = KlassRepository();// 实例化学生服务和学生仓库,从而查看每个班级里的学生StudentService studentService = new StudentService();private StudentRepository studentRepository = new StudentRepository();此外,C层调用Service时,也须要实例化它: StudentService studentService = new StudentService();这样一来,的确能够通过new对象实现组件间依赖关系,但问题也很显著。 局限性从业务逻辑上看,控制器、服务、仓库,都是负责数据流的解决和传递,所以应该都是单例模式,反复的实例化这些对象除了耗费多余资源以外,更重要的是会烦扰内局部变量的失常调用。所以齐全能够让全局共享同一个依赖关系的实例对象。 有人可能会说,那能够持续改良一下,只在某个特定的组件中实现它的依赖关系调用,其余的组件共享这些对象。但因为业务逻辑的不确定性,编写时很难确定组件的生命周期,谁来创立、何时创立、何时开释、开释时是否保障它曾经不被调用了,这些都是问题。 二、管制反转(Inversion of Control)管制反转的字面意思是:原本保护对象的工作由开发者实现,所谓反转,就是把这个过程交给程序本身实现。开发者只须要通知Spring对象之间的依赖关系即可,实现细节由Spring通过反射的形式主动实现。人为规定,咱们把通过Spring IoC创立的组件叫做Bean。 上面有三种依赖注入的形式: ① set办法注入例如StudentController中注入StudentService // 第一步,在类中申明依赖关系StudentService studentService;// 第二步,在类中申明一个set办法public void setStudentService(StudentService studentService) { this.studentService= studentService;}// 第三步,在xml中”通知“Spring如何依赖<?xml version="1.0" encoding="UTF-8"?><beans> <bean id="StudentController" class="test.lyx6666.controller.StudentController"> <property name="studentService" ref="studentService" /> </bean> <bean id="studentService" class="test.lyx6666.controller.studentService" /></beans>但这种形式还是比拟麻烦。 ...

March 7, 2022 · 1 min · jiezi

关于ioc:IOCBean管理xml集合类型属性注入

Array或List或Map <bean id="stu" class="com.zong.spring.Student"> <property name="courses"> <array> <value>语文</value> <value>数学</value> </array> </property> <property name="scores"> <list> <value>100</value> <value>120</value> </list> </property> <property name="scores"> <map> <entry key="语文" value="100"></entry> <entry key="数学" value="120"></entry > </map> </property> <property name="scores"> <set> <value>100</value> <value>120</value> </set> </property> //元素是对象类型 <property name="courses"> <array> <ref bean="course1"></ref> </array> </property></bean> <bean id="course1" class="com.zong.spring.Course"> <property name="cname" value="语文"></property> </bean>//提取公共汇合 <util:list id="booklist"> <value>云边有个小卖部</value> </util:list> <bean id="book" class="com.zong.spring.Book"> <property name="bookList" ref="booklist"></property> </bean>

June 13, 2021 · 1 min · jiezi

关于ioc:数栈技术分享聊聊IOC中依赖注入那些事-Dependency-inject

Part1: What is Dependency injection 依赖注入定义为组件之间依赖关系由容器在运行期决定,形象的说即由容器动静的将某个依赖关系注入到组件之中在面向对象编程中,咱们常常解决的问题就是解耦,管制反转(IoC)就是罕用的面向对象编程的设计准则,其中依赖注入是管制反转最罕用的实现。指标解决以后类不负责被依赖类实例的创立和初始化。Part2: What is Dependency 依赖是程序中常见的景象,假如有 A和B都被C耦合依赖着,在 OOP 编程中依赖无处不在。依赖模式有多种表现形式,比方一个类向另一个类发消息,一个类是另一个类的成员,一个类是另一个类的参数。 class A {} class B { classA: A; constructor() { this.classA = new A();}} class C { classA: A; classB: B; constructor() { this.classA = new A();this.classB = new B();}} Part3: When is use Dependency injection eg: 以用户调用 API 层打印日志来阐明 LoggerService被ApiService和UserService所依赖ApiService被UserService所依赖class LoggerService { constructor() {}log(args) { console.log(args)}} class ApiService { constructor ( private readonly logger: LoggerService) { this.logger.log('api constructor')}public async getMydata () { return { name: 'mumiao', hobby: 'focusing in web'}}} ...

May 19, 2021 · 4 min · jiezi

依赖注入dependency-injection最通俗的讲解

这篇文章解释了什么是依赖注入(又称控制反转),以及它如何改善定义业务逻辑的代码。 服务和依赖服务可以是您编写的类,也可以是来自导入库的类。例如,它可以是一个 logger 或一个 database connection。因此,您可以编写一个无需任何外部帮助即可单独运行的服务,但也可能您会很快您会达到一个点,即其中一个服务将不得不使用另一个服务的代码的地步。 让我们看一个小的例子 我们将创建一个EmailSender。此类将用于发送电子邮件。它必须在数据库中写入已发送电子邮件的信息,并记录可能发生的错误。 EmailSender 将依赖于其他三项服务:用于发送电子邮件的 SmtpClient,用于与数据库交互的 EmailRepository 以及用于记录错误的 Logger。 通常情况下我们会怎么实现呢? 1.EmailSender 的三个依赖关系被声明为属性。依赖项注入的思想是,EmailSender不应该负责创建其依赖项,它们应该从外部注入。对EmailSender来说,其配置的详细信息应该是未知的。 interface SmtpClientInterface { send(toName: string, toEmail: string, subject: string, message: string)}interface EmailRepositoryInterface { insertEmail(address: string, email: Email, status: string) updateEmailStatus(id: number, status: string)}interface LoggerInterface { error(message: string)}class EmailSender { client: SmtpClientInterface repo: EmailRepositoryInterface logger: LoggerInterface send(user: User, email: Email) { try { this.repo.insertEmail(user.email, email, "sending") this.client.send(user.email, user.name, email.subject, email.message) this.repo.updateEmailStatus(email.id, "sent") } catch(e) { this.logger.error(e.toString()) } }}2.使用 setter,您可以在EmailSender上添加setSmtpClient(),setEmailRepository()和setLogger()方法。 ...

September 30, 2019 · 8 min · jiezi

Spring-IOC过程源码解析

废话不多说,我们先做一个傻瓜版的IOC demo作为例子自定义的Bean定义 class MyBeanDefinition{ public String id; public String className; public String value; public MyBeanDefinition(String id, String className, String value) { this.id = id; this.className = className; this.value = value; }}自定义的Bean工厂 class MyBeanFactory { Map<String, Object> beanMap = new HashMap<>(); public MyBeanFactory(MyBeanDefinition beanDefinition) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class<?> beanClass = Class.forName(beanDefinition.className); Object bean = beanClass.newInstance(); ((UserService) bean).setName(beanDefinition.value); beanMap.put(beanDefinition.id, bean); } public Object getBean(String id) { return beanMap.get(id); }}测试傻瓜版IOC容器 ...

September 10, 2019 · 9 min · jiezi

如何理解-Laravel-和-ThinkPHP-5-中的服务容器与注入

从文档说起很多人一开始看到官方的文档,无论是 Laravel 还是 ThinkPHP ,看完都是一头雾水,不求甚解。甚至都是直接跳过去,不看,反正我也不一样用得到这么高端的东西,如果在短时间内有这个念头很正常,尤其是习惯了 ThinkPHP 3 的使用者,相对引入的理念比较前沿,如果你在长时间内都不去考虑去理解,那就要看你自己的职业规划了。接下来就来一起看一下,细细追品。 从 Laravel 开始从 Laravel 的文档中看到有 bind 、 singleton 以及 instance ,这三个常用方法,接下来就一一解答。 实际应用假设我们有这样一个场景,当我们用户在进行注册时,我们需要向用户手机发送一条短信验证码,然后当用户收到验证码后在注册表单提交时还需要验证验证码是否正确。 这个需求看起来非常容易实现,对吧? 当我们拿到短信平台的开发文档后,我们只需要写出两个方法。send 和 check 分别用来发送验证码和校验验证码,下面就在不用容器的情况下来写一下伪代码。 MeiSms.php<?phpnamespace App\Tools;class MeiSms{ public function send($phone) { $code = mt_rand(1e3, 1e4 - 1); // TODO ... 通过接口发送 // 存放验证码到 Redis $cacheManager = cache(); // 设定 5 分钟失效 $cacheManager->set('sms:' . $phone, $code, 5 * 60); return true; } public function check($phone, $code) { $cacheManager = cache(); return $cacheManager->get('sms:' . $phone) === $code; }}很容易,不是吗? ...

July 3, 2019 · 4 min · jiezi

搞懂依赖注入, 用 PHP 手写简易 IOC 容器

前言好的设计会提高程序的可复用性和可维护性,也间接的提高了开发人员的生产力。今天,我们就来说一下在很多框架中都使用的依赖注入。 一些概念要搞清楚什么是依赖注入如何依赖注入,首先我们要明确一些概念。 DIP (Dependence Inversion Principle) 依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体实现。 IOC (Inversion of Control) 控制反转:遵循依赖倒置原则的一种代码设计方案,依赖的创建 (控制) 由主动变为被动 (反转)。 DI (Dependency Injection) 依赖注入:控制反转的一种具体实现方法。通过参数的方式从外部传入依赖,将依赖的创建由主动变为被动 (实现了控制反转)。 光说理论有点不好理解,我们用代码举个例子。 首先,我们看依赖没有倒置时的一段代码: class Controller{ protected $service; public function __construct() { // 主动创建依赖 $this->service = new Service(12, 13); } }class Service{ protected $model; protected $count; public function __construct($param1, $param2) { $this->count = $param1 + $param2; // 主动创建依赖 $this->model = new Model('test_table'); }}class Model{ protected $table; public function __construct($table) { $this->table = $table; }}$controller = new Controller;上述代码的依赖关系是 Controller 依赖 Service,Service 依赖 Model。从控制的角度来看,Controller 主动创建依赖 Service,Service 主动创建依赖 Model。依赖是由需求方内部产生的,需求方需要关心依赖的具体实现。这样的设计使代码耦合性变高,每次底层发生改变(如参数变动),顶层就必须修改代码。 ...

April 22, 2019 · 4 min · jiezi

手动实现“低配版”IOC

引言IOC,全称Inversion of Control,控制反转。也算是老生常谈了。老生常谈:原指老书生的平凡议论;今指常讲的没有新意的老话。同时另一个话题,依赖注入,用什么就声明什么,直接就声明,或者构造函数或者加注解,控制反转是实现依赖注入的一种方式。通过依赖注入:我们无需管理对象的创建,通过控制反转:我们可以一键修改注入的对象。最近在做Android实验与小程序相关的开发,发现用惯了IOC的我们再去手动new对象的时候总感觉心里不舒服,以后改起来怎么办呢?既然没有IOC,我们就自己写一个吧。实现就像我在标题中描述的一样,我先给大家讲解一下标配IOC的原理,使大家更清晰明了,但是受Android与小程序相关的限制,我具体的实现,是低配版IOC。个人扯淡不管是Spring还是Angular,要么是开源大家,要么是商业巨头,具体的框架实现都是相当优秀。我还没水平也没精力去研读源码,只希望分享自己对IOC的理解,帮到更多的人。毕竟现在小学生都开始写Python,以后的框架设计会越来越优秀,学习成本越来越低,开发效率越来越高。可能是个人报个培训班学个俩月也会设计微服务,也能成为全栈工程师。所以我们应该想的是如何设计框架,而不是仅停留在使用的层面,渐渐地被只会写增删改查天天搬砖的人取代。996加班的工程师都开始挤时间写框架扩大影响力,让社会听到程序员的呐喊,我们还有什么不努力的理由?“标配”IOC不管是Spring还是Angular,它们的核心是什么呢?打上mvn spring-boot:run后台就Started Application in xxx seconds了,它到底干什么了呢?容器Spring与Angular就是一个大的IOC容器,所以应用启动的过程,其实就是构造容器的过程。容器,肯定是装东西的啊?IOC容器里装的是什么?装的是对象。控制器Controller,服务Service,自定义的组件Component,所有被Spring管理的对象都将被放进IOC容器里。所以,大家应该能明白,为什么IOC是依赖注入的一种实现方式?因为这个对象不是你自己new的,是从容器中拿的,容器初始化的时候,就已经把这个对象构造好了,该注的都注进来了。思考从上面大家可以看到,依赖注入的前提是什么?是要求这个对象必须是从容器中拿的,所以才能依赖注入成功。Spring Boot中没问题,Tomcat转发的路由直接交给容器中相应的对象去处理,同理,Angular也一样。Android呢?手机调用的并不是我们构造的Activity,而是它自己实例化的,小程序也与之类似,Page的实例化不归我们管。所以“标配”IOC在这里不适用,所以我设计了“低配”IOC容器。“低配”IOC找点自己能管得了的对象放在IOC容器里,这样再需要对象就不用去new了,Service有变更直接修改注入就行了。受我们管理的只有Service,计划设计一个管理所有Service的IOC容器,然后Activity或Page里用的时候,直接从容器中拿。(低配在这里,不能依赖注入了,得自己拿)。Android端一个单例的Configuration负责注册Bean和获取Bean,存储着一个context上下文。看着挺高级的,其实就是一个HashMap存储着接口类型到容器对象的映射。/** * 全局配置类 /public class Configuration { private static Map<Class<?>, Object> context = new HashMap<>(); private static final class Holder { private static final Configuration INSTANCE = new Configuration(); } public static Configuration getInstance() { return Holder.INSTANCE; } public Configuration registerBean(Class<?> clazz, Object bean) { context.put(clazz, bean); return this; } public <T> T getBean(Class<?> clazz) { return (T) context.get(clazz); }}写一个静态方法,更加方便配置。/* * 云智,全局配置辅助类 */public class Yunzhi { … public static <T> T getBean(Class<?> clazz) { return Configuration.getInstance().getBean(clazz); }}一个Application负责容器中所有对象的创建。public class App extends Application { @Override public void onCreate() { super.onCreate(); Yunzhi.init() .setApi(“http://192.168.2.110:8888”) .setTimeout(1L) .registerBean(AuthService.class, new AuthServiceImpl()) .registerBean(LetterService.class, new LetterServiceImpl()); }}使用方法和原来就一样了,一个接口,一个实现类。这里用到了RxJava,看上去倒是类似我们的Angular了。public interface AuthService { Observable<Auth> login(String username, String password);}public class AuthServiceImpl implements AuthService { private static final String TAG = “AuthServiceImpl”; @Override public Observable<Auth> login(String username, String password) { Log.d(TAG, “BASIC 认证”); String credentials = username + “:” + password; String basicAuth = “Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP); Log.d(TAG, “请求登录”); return HttpClient.request(AuthRequest.class) .login(basicAuth) .subscribeOn(Schedulers.io()) // 在IO线程发起网络请求 .observeOn(AndroidSchedulers.mainThread()); // 在主线程处理 }}因为Activity我们管不着,所以在Activity里用不了依赖注入,需要手动从容器里拿。@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.authService = Yunzhi.getBean(AuthService.class);}这里有一处没考虑到的问题,就是手机端的性能问题,手机和服务器的处理能力肯定是比不了的,服务器在初始化的时候把所有对象都创建处理无可厚非,但是感觉手机端这样做还是会对性能产生一定影响的。应该是某些对象用到的时候再创建,实验要求时间很紧,这个就先这样吧,不改了。小程序端小程序是在Android之后写的,想到了之前设计的部分缺陷,对容器使用了另一种思想进行实现。export class YunzhiService { private static context = new Map<string, object>(); public static getBean(beanName: string): object { // 从context里拿对象 let bean = this.context.get(beanName); // 如果没有,构造一个,并放进context if (!bean) { bean = this.createBeanByName(beanName); this.context.set(beanName, bean); } // 返回 return bean; } public static createBeanByName(beanName: string): object { // 根据不同名称构造不同的Bean switch (beanName) { case Bean.AUTH_SERVICE: return new AuthServiceImpl(); case Bean.SCORE_SERVICE: return new ScoreServiceImpl(); case Bean.SEMESTER_SERVICE: return new SemesterServiceImpl(); default: throw ‘错误,未注册的bean’; } }}总结Spring Boot真厉害,什么时候我们也能写出如此优秀的框架? ...

April 3, 2019 · 2 min · jiezi

为了学习理解依赖注入和路由,自己撸了一个PHP框架

如何提高自己编写代码的能力呢?作为web开发者,我们通常都是基于面向对象OOP来开发的,所以面向对象的设计能力或者说设计模式的运用能力尤为重要,当然还有开发语言本身特性和基础的灵活运用。我们可以去阅读一些优秀的开源项目,理解里面的代码设计,去学习和造轮子来提高自己。我比较关注web framework中的路由、HTTP、依赖注入容器这几部分,路由和http处理是web框架必不可少的,整个框架的服务对象依赖解析也是很重要的,有了依赖注入容器可以实现类很好的解耦。Dependency Injection Container先来说下什么是依赖注入,依赖注入是一种允许我们从硬编码的依赖中解耦出来,从而在运行时或者编译时能够修改的软件设计模式(来自维基百科 Wikipedia)。依赖注入通过构造注入,函数调用或者属性的设置来提供组件的依赖关系。下面的代码中有一个 Database 的类,它需要一个适配器来与数据库交互。我们在构造函数里实例化了适配器,从而产生了耦合。这会使测试变得很困难,而且 Database 类和适配器耦合的很紧密。<?phpnamespace Database;class Database{ protected $adapter; public function __construct() { $this->adapter = new MySqlAdapter; }}class MysqlAdapter {}这段代码可以用依赖注入重构,从而解耦<?phpnamespace Database;class Database{ protected $adapter; public function __construct(MySqlAdapter $adapter) { $this->adapter = $adapter; }}class MysqlAdapter {}现在我们通过外界给予 Database 类的依赖,而不是让它自己产生依赖的对象。我们甚至能用可以接受依赖对象参数的成员函数来设置,或者如果 $adapter 属性本身是 public的,我们可以直接给它赋值。根据依赖注入的概念,我们的框架实现了这些特性。Dependency injection Container基于PSR-11规范实现,包括3种注入实现方式:构造方法注入(Constructor Injection)、setter方法或属性注入(Setter Injection)、匿名回调函数注入,代码如下:1.构造方法注入(Constructor Injection)<?php declare(strict_types=1);namespace Examples;use Eagle\DI\Container;class Foo{ /** * @var \Examples\Bar / public $bar; /* * Foo constructor. * @param \Examples\Bar $bar / public function __construct(Bar $bar) { $this->bar = $bar; }}/class Bar {}/class Bar { public $baz; public function __construct(Baz $baz) { $this->baz = $baz; }}class Baz {}$container = new Container;$container->set(Foo::class)->addArguments(Bar::class);$container->set(Bar::class)->addArguments(Baz::class);$foo = $container->get(Foo::class);var_dump($foo, $foo->bar);var_dump($foo instanceof Foo); // truevar_dump($foo->bar instanceof Bar); // truevar_dump($foo->bar->baz instanceof Baz); // true2.方法注入<?phpdeclare(strict_types=1);namespace Examples;require ‘vendor/autoload.php’;use Eagle\DI\Container;class Controller{ public $model; public function __construct(Model $model) { $this->model = $model; }}class Model{ public $pdo; public function setPdo(\PDO $pdo) { $this->pdo = $pdo; }}$container = new Container;$container->set(Controller::class)->addArguments(Model::class);$container->set(Model::class)->addInvokeMethod(‘setPdo’, [\PDO::class]);$container->set(\PDO::class) ->addArguments([‘mysql:dbname=test;host=localhost’, ‘root’, ‘111111’]);$controller = $container->get(Controller::class);var_dump($controller instanceof Controller); // truevar_dump($controller->model instanceof Model); // truevar_dump($controller->model->pdo instanceof \PDO); // true3.匿名回调函数注入<?phpdeclare(strict_types=1);namespace Examples;require ‘vendor/autoload.php’;use Eagle\DI\Container;class Controller{ public $model; public function __construct(Model $model) { $this->model = $model; }}class Model{ public $pdo; public function setPdo(\PDO $pdo) { $this->pdo = $pdo; }}$container = new Container;$container->set(Controller::class, function () { $pdo = new \PDO(‘mysql:dbname=test;host=localhost’, ‘root’, ‘111111’); $model = new Model; $model->setPdo($pdo); return new Controller($model);});$controller = $container->get(Controller::class);var_dump($controller instanceof Controller); // truevar_dump($controller->model instanceof Model); // truevar_dump($controller->model->pdo instanceof \PDO); // true自动布线 (auto wiring)<?phpdeclare(strict_types=1);namespace AutoWiring;require ‘vendor/autoload.php’;use Eagle\DI\ContainerBuilder;class Foo{ /* * @var \AutoWiring\Bar / public $bar; /* * @var \AutoWiring\Baz / public $baz; /* * Construct. * * @param \AutoWiring\Bar $bar * @param \AutoWiring\Baz $baz / public function __construct(Bar $bar, Baz $baz) { $this->bar = $bar; $this->baz = $baz; }}class Bar{ /* * @var \AutoWiring\Bam / public $bam; /* * Construct. * * @param \AutoWiring\Bam $bam */ public function __construct(Bam $bam) { $this->bam = $bam; }}class Baz{ // ..}class Bam{ // ..}$container = new ContainerBuilder;$container = $container->build();$foo = $container->get(Foo::class);var_dump($foo instanceof Foo); // truevar_dump($foo->bar instanceof Bar); // truevar_dump($foo->baz instanceof Baz); // truevar_dump($foo->bar->bam instanceof Bam); // trueRoute再介绍下路由使用的例子,route可以使用symfony的http foundation组件来处理HTTP请求(http messages)。<?phprequire ‘vendor/autoload.php’;use Eagle\Route\Router;use Symfony\Component\HttpFoundation\Request;$router = new Router();$router->get(’/articles’, function () { return ‘This is articles list’;});$router->get(’/articles/{id:\d+}’, function ($id) { return ‘Article id: ’ . $id;});$router->get(’/articles/{id:\d+}[/{title}]’, function ($id, $title) { return ‘Article id: ’ . $id . ‘, title: ’ . $title;});/匹配处理路由组/$router->group(’/articles’, function () use ($router) { $router->get(’/list’, function() { return ‘This is articles list’; }); $router->get(’/detail’, function ($id, $title) { return ‘Article detail id: ’ . $id . ‘, title: ’ . $title; });});$request = new Request();$routeHandler = $router->getRouteHandler();$response = $routeHandler->handle($request);echo $response;其它的ORM、cache、filesystem、session、validation等组件可以使用composer来由用户自由扩展。项目地址 https://github.com/parvinShi/… ...

March 11, 2019 · 2 min · jiezi

设计一个可拔插的 IOC 容器

前言磨了许久,借助最近的一次通宵上线 cicada 终于更新了 v2.0.0 版本。之所以大的版本号变为 2,确实是向下不兼容了;主要表现为:修复了几个反馈的 bug。灵活的路由方式。可拔插的 IOC 容器选择。其中重点是后面两个。新的路由方式先来看第一个:路由方式的更新。在之前的版本想要写一个接口必须的实现一个 WorkAction;而且最麻烦的是一个实现类只能做一个接口。因此也有朋友给我提过这个 issue。于是改进后的使用方式如下:是否有点似曾相识的感觉????。如上图所示,不需要实现某个特定的接口;只需要使用不同的注解即可。同时也支持自定义 pojo, cicada 会在调用过程中对参数进行实例化。拿这个 getUser 接口为例,当这样请求时这些参数就会被封装进 DemoReq 中.http://127.0.0.1:5688/cicada-example/routeAction/getUser?id=1234&name=zhangsan同时得到响应:{“message”:“hello =zhangsan”}实现过程也挺简单,大家查看源码便会发现;这里贴一点比较核心的步骤。扫描所有使用 @CicadaAction 注解的类。扫描所有使用 @CicadaRoute 注解的方法。将他们的映射关系存入 Map 中。请求时根据 URL 去 Map 中查找这个关系。反射构建参数及方法调用。扫描类以及写入映射关系请求时查询映射关系反射调用这些方法是否需要 IOC 容器上面那几个步骤其实我都是一把梭写完的,但当我写到执行具体方法时感觉有点意思了。大家都知道反射调用方法有两个重要的参数:obj 方法执行的实例。args.. 自然是方法的参数。我第一次写的时候是这样的:method.invoke(method.getDeclaringClass().newInstance(), object);然后一测试,也没问题。当我写完之后 review 代码时发现不对:这样这里每次都会创建一个新的实例,而且反射调用 newInstance() 效率也不高。这时我不自觉的想到了 Spring 中 IOC 容器,和这里场景也非常的类似。在应用初始化时将所有的接口实例化并保存到 bean 容器中,当需要使用时只需要从容器中获取即可。这样只是会在启动时做很多加载工作,但造福后代啊。可拔插的 IOC 容器于是我打算自己实现一个这样的 bean 容器。但在实现之前又想到一个 feature:不如把实现 bean 容器的方案交给使用者选择,可以选择使用 bean 容器,也可以就用之前的每次都创建新的实例,就像 Spring 中的 prototype 作用域一样。甚至可以自定义容器实现,比如将 bean 存放到数据库、Redis 都行;当然一般人也不会这么干。和 SPI 的机制也有点类似。要实现上述的需求大致需要以下步骤:一个通用的接口,包含了注册容器、从容器中获取实例等方法。BeanManager 类,由它来管理具体使用哪种 IOC 容器。所以首先定义了一个接口;CicadaBeanFactory:包含了注册和获取实例的接口。同时分别有两个不同的容器实现方案。默认实现;CicadaDefaultBean:也就是文中说道的,每次都会创建实例;由于这种方式其实根本就没有 bean 容器,所以也不存在注册了。接下来是真正的 IOC 容器;CicadaIoc:它将所有的实例都存放在一个 Map 中。当然也少不了刚才提到的 CicadaBeanManager,它会在应用启动的时候将所有的实例注册到 bean 容器中。重点是图中标红的部分:需要根据用户的选择实例化 CicadaBeanFactory 接口。将所有的实例注册到 CicadaBeanFactory 接口中。同时也提供了一个获取实例的方法:就是直接调用 CicadaBeanFactory 接口的方法。然后在上文提到的反射调用方法处就变为:从 bean 容器中获取实例了;获取的过程可以是每次都创建一个新的对象,也可以是直接从容器中获取实例。这点对于这里的调用者来说并不关心。所以这也实现了标题所说的:可拔插。为了实现这个目的,我将 CicadaIoc 的实现单独放到一个模块中,以 jar 包的形式提供实现。所以如果你想要使用 IOC 容器的方式获取实例时只需要在你的应用中额外加入这个 jar 包即可。<dependency> <groupId>top.crossoverjie.opensource</groupId> <artifactId>cicada-ioc</artifactId> <version>2.0.0</version></dependency>如果不使用则是默认的 CicadaDefaultBean 实现,也就是每次都会创建对象。这样有个好处:当你自己想实现一个 IOC 容器时;只需要实现 cicada 提供的 CicadaBeanFactory 接口,并在你的应用中只加入你的 jar 包即可。其余所有的代码都不需要改变,便可随意切换不的容器。当然我是推荐大家使用 IOC 容器的(其实就是单例),牺牲一点应用启动时间带来后续性能的提升是值得的。总结cicada 的大坑填的差不多了,后续也会做一些小功能的迭代。还没有关注的朋友赶紧关注一波:https://github.com/TogetherOS/cicadaPS:虽然没有仔细分析 Spring IOC 的实现,但相信看完此篇的朋友应该对 Spring IOC 以及 SpringMVC 会有一些自己的理解。你的点赞与分享是对我最大的支持 ...

November 15, 2018 · 1 min · jiezi