zoukankan      html  css  js  c++  java
  • 2spring中ioc,di加强理解,和简单的代码实现-----主要是解耦合----增加AOP

    转自https://blog.csdn.net/qq_38157516/article/details/81979219

    1. IoC理论的背景
    我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。
    图1:软件系统中耦合的对象
    如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间。图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。
    齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。
    图2:对象之间复杂的依赖关系
    耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中,很多的J2EE项目均采用了IOC框架产品Spring。
    2. 什么是控制反转(IoC)
    IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”,还有些书籍翻译成为“控制反向”或者“控制倒置”。
    1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。对于面向对象设计及编程的基本思想,前面我们已经讲了很多了,不再赘述,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,如下图:

    图3:IOC解耦过程
    大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
    我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:

    图4:拿掉IoC容器后的系统
    我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!
    我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:
    软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
    软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
    通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
    3. IOC的别名:依赖注入(DI)
    2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
    所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

    4. IOC为我们带来了什么好处
    我们还是从USB的例子说起,使用USB外部设备比使用内置硬盘,到底带来什么好处?
    第一、USB设备作为电脑主机的外部设备,在插入主机之前,与电脑主机没有任何的关系,只有被我们连接在一起之后,两者才发生联系,具有相关性。所以,无论两者中的任何一方出现什么的问题,都不会影响另一方的运行。这种特性体现在软件工程中,就是可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。代码中的每一个Class都可以单独测试,彼此之间互不影响,只要保证自身的功能无误即可,这就是组件之间低耦合或者无耦合带来的好处。
    第二、USB设备和电脑主机的之间无关性,还带来了另外一个好处,生产USB设备的厂商和生产电脑主机的厂商完全可以是互不相干的人,各干各事,他们之间唯一需要遵守的就是USB接口标准。这种特性体现在软件开发过程中,好处可是太大了。每个开发团队的成员都只需要关心实现自身的业务逻辑,完全不用去关心其它的人工作进展,因为你的任务跟别人没有任何关系,你的任务可以单独测试,你的任务也不用依赖于别人的组件,再也不用扯不清责任了。所以,在一个大中型项目中,团队成员分工明确、责任明晰,很容易将一个大的任务划分为细小的任务,开发效率和产品质量必将得到大幅度的提高。
    第三、同一个USB外部设备可以插接到任何支持USB的设备,可以插接到电脑主机,也可以插接到DV机,USB外部设备可以被反复利用。在软件工程中,这种特性就是可复用性好,我们可以把具有普遍性的常用组件独立出来,反复利用到项目中的其它部分,或者是其它项目,当然这也是面向对象的基本特征。显然,IOC不仅更好地贯彻了这个原则,提高了模块的可复用性。符合接口标准的实现,都可以插接到支持此标准的模块中。
    第四、同USB外部设备一样,模块具有热插拔特性。IOC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性。
    以上几点好处,难道还不足以打动我们,让我们在项目开发过程中使用IOC框架吗?
    5. IOC容器的技术剖析
    IOC中最基本的技术就是“反射(Reflection)”编程,目前.Net C#、Java和PHP5等语言均支持,其中PHP5的技术书籍中,有时候也被翻译成“映射”。有关反射的概念和用法,大家应该都很清楚,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。反射的应用是很广泛的,很多的成熟的框架,比如象Java中的Hibernate、Spring框架,.Net中 NHibernate、Spring.Net框架都是把“反射”做为最基本的技术手段。
    反射技术其实很早就出现了,但一直被忽略,没有被进一步的利用。当时的反射编程方式相对于正常的对象生成方式要慢至少得10倍。现在的反射技术经过改良优化,已经非常成熟,反射方式生成对象和通常对象生成方式,速度已经相差不大了,大约为1-2倍的差距。
    我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
    6. IOC容器的一些产品
    Sun ONE技术体系下的IOC容器有:轻量级的有Spring、Guice、Pico Container、Avalon、HiveMind;重量级的有EJB;不轻不重的有JBoss,Jdon等等。Spring框架作为Java开发中SSH(Struts、Spring、Hibernate)三剑客之一,大中小项目中都有使用,非常成熟,应用广泛,EJB在关键性的工业级项目中也被使用,比如某些电信业务。
    .Net技术体系下的IOC容器有:Spring.Net、Castle等等。Spring.Net是从Java的Spring移植过来的IOC容器,Castle的IOC容器就是Windsor部分。它们均是轻量级的框架,比较成熟,其中Spring.Net已经被逐渐应用于各种项目中。
    7. 使用IOC框架应该注意什么
    使用IOC框架产品能够给我们的开发过程带来很大的好处,但是也要充分认识引入IOC框架的缺点,做到心中有数,杜绝滥用框架。
    第一、软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
    第二、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
    第三、具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
    第四、IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
    我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深入的理解,也不要贸然引入。最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品,象WEB2.0网站就是这种情况。

    ioc di的简单实现

           1    一个pojo  category   private int id; private String name;
    
       2    spring相关jar包    
    
       3    一个xml <bean name="c" class="com.how2java.pojo.Category">
                        <property name="name" value="category 1" />
            </bean>    
           4    一个main ApplicationContext context = new ClassPathXmlApplicationContext(
                    new String[] { "applicationContext.xml" });
     
            Category c = (Category) context.getBean("c");
             
            System.out.println(c.getName());    

    主要是解耦合

    spring+IOC+DI

    依赖包加
    package pojo;
    
    /**
     * @description:
     * @author: 范子祺
     **/
    public class Category {
    
        private int id;
        private String name;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
    
    
    
    public class Product {
        private int id;
        private String name;
        private  Category category;
    }
    
    
    
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <bean name="cc" class="pojo.Category">
            <property name="name" value="test spring DI" />
        </bean>
        <bean name="pp" class="pojo.Product">
            <property name="name" value="product testIOC"/>
            <property name="category" ref="cc"/>
        </bean>
    
    </beans>
    
    
    
    
    
    
    package test;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import pojo.Product;
    
    /**
     * @description:
     * @author: 范子祺
     **/
    public class TestSpring {
        public static void main(String[] args){
            ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
            Product p = (Product)context.getBean("pp");
    
            System.out.println(p.getName());
            System.out.println(p.getCategory().getName());
        }
    }
    View Code

    通过注解+配置文件实现IOC+DI

    配置文件 
    <context:annotation-config/>
    
        <bean name="cc" class="pojo.Category">
            <property name="name" value="test spring DI" />
        </bean>
        <bean name="pp" class="pojo.Product">
            <property name="name" value="product testIOC"/>
    
        </bean>
    
    
    实体中加注解
    public class Product {
        private int id;
        private String name;
        @Autowired
        private  Category category;
    
    
    main
    public static void main(String[] args){
            ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
            Product p = (Product)context.getBean("pp");
    
            System.out.println(p.getName());
            System.out.println(p.getCategory().getName());
        }
    View Code

    在setCategory方法前加上@Autowired

    public class Product {
        private int id;
        private String name;
    //    @Autowired
        private  Category category;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Category getCategory() {
            return category;
        }
    
        @Autowired
        public void setCategory(Category category) {
            this.category = category;
        }
    }
    View Code

    @Resource

    public class Product {
        private int id;
        private String name;
    //    @Autowired
        @Resource(name="cc")
        private  Category category;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Category getCategory() {
            return category;
        }
    
    //    @Autowired
        public void setCategory(Category category) {
            this.category = category;
        }
    }
    View Code

    上述例子是对注入对象行为的注解,那么bean对象本身,比如Category,Product可不可以移出applicationContext.xml配置文件,也通过注解进行呢?
    接下来就讲解如何对Bean进行注解配置

    感觉这个不太好写的

    category
    @Component("cc")
    public class Category {
    
        private int id;
    //    private String name;
        private String name = "category DI";
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
    product
    
    @Component("pp")
    public class Product {
        private int id;
        private String name="product IOC";
    //    private String name;
    //    @Autowired
    //    @Resource(name="cc")
    
        @Autowired
        private  Category category;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Category getCategory() {
            return category;
        }
    
    //    @Autowired
        public void setCategory(Category category) {
            this.category = category;
        }
    
    
    
    配置文件只留
    <context:component-scan base-package="pojo"/>
    
    
    main不变
    View Code

    springAOP编程思想

    简单的看了一下,有空仔细研究

    阅读本文章的前提:java基础,具备java面向对象思想
    
    深入学习需要了解知识点:jdk 静态代理,动态代理,java继承,多态,接口
    
    
    
    AOP:面向切面编程思想是继OOP面向对象编程思想后,又一个伟大思想。当你意识到这句话的时候,你大概已经理解了什么是AOP;
    
    
    
    什么是切面:程序运行流程中的某一个特定点,在这个点你可以切入到原本的流程中,并且加入新的业务逻辑。
    
    
    
    传统切面:找到切入点后,修改切入点前后程序代码,进而加入新的逻辑。
    
    AOP的核心是:源代码无关性!也就是说,你加入的逻辑代码和业务代码是无关的两部分代码,只在程序运行期间,你可以加入插入的逻辑,也可以不加入。
    
    
    
    打个比方,AOP的切面就是创建了一个原来系统的分身。你可以任意修改系统分身,而不影响本体,运行修改后分身,你得到了想要的结果,原本的业务逻辑是没有任何变化的。
    
    
    
    这样做的好处:源代码的无关性,主要业务逻辑和其他业务逻辑完全分离,耦合度极大降低。
    
    
    
    实现AOP依赖的技术:JDK动态代理。我简单介绍代理,静态代理,动态代理,深入的了解大家可以参照其他大神。
    
    
    
    代理:代理的本质就是分身。
    
    为什么要用代理:你不想或者不能直接做某件事,就要用到代理。比如:你到监狱想给一个犯人一个包裹,你是不能直接接触犯人的,那么你需要通过预警传递包裹。那么这个预警就是代理。从取包裹来讲,狱警就是犯人的分身,这个分身代替本体取得了包裹。如果这个包裹有炸弹,受害的是分身,而本体是不会受到影响的。
    
    
    
    静态代理和动态代理本质区别:你是否需要修改源代码(不包含测试类),就可以控制代理内容的变化。
    
    
    
    静态代理:程序写好之后,你想修改代理过程,就必须修改源代码。
    
    动态代理:程序写好后,你更改代理过程。只需要修改代理对象(分身)
    
    
    
    上面可能不好理解,理解重点是:为什么动态代理这么牛,他有什么过人之处。
    
    答案就在前面,静态代理执行过程中,实际修改了源代码,你改变过程就需要改变源代码。
    
    动态代理则不同,动态代理copy了一份源代码,并且创建了分身。如果更改过程,我没有修改源代码,只是修改了copy的代码,并且创建了另外一个分身。
    
    这个分身实现了效果,而这个分身无论生死,和本体源代码无关。
    
    
    
    前面狱警的例子就是动态代理,如果想取另外一个包裹,告诉狱警(犯人的分身)去做就好。过程中出现任何变动,都仅仅影响分身。
    
    静态代理的意思就是:我吃了一种药,胳膊编的无限长(超出原来的部分就是静态代理),我用自己的胳膊去取。如果被炸弹炸了,我的胳膊就没了。
    
    
    
    整个过程,我们认识到,动态代理我还是原来的我。而静态代理实现这个动能,已经把我变成怪物了。
    
    
    
    
    
    最后,我只是简单说了下我的理解。想具体使用,请学习jdk 动态代理。和aop思想
    --------------------- 
    作者:如何在3年拿到50K 
    来源:CSDN 
    原文:https://blog.csdn.net/kouryoushine/article/details/77504222 
    版权声明:本文为博主原创文章,转载请附上博文链接!
    View Code

    代码

    核心业务
    package service;
    
    /**
     * @description:
     * @author: 范子祺
     **/
    public class ProductService {
        public void doSomeService(){
    
            System.out.println("核心业务");
    
        }
    }
    
    
    切面业务
    package AOP;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    /**
     * @description:
     * @author: 范子祺
     **/
    public class LoggerAspect {
        public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("start log:" + joinPoint.getSignature().getName());
            Object object = joinPoint.proceed();
            System.out.println("end log:" + joinPoint.getSignature().getName());
            return object;
    
        }
    }
    
    
    业务调用
    package test;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import service.ProductService;
    
    /**
     * @description:
     * @author: 范子祺
     **/
    public class TestAOP {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
            ProductService s = (ProductService) context.getBean("s");
            s.doSomeService();
        }
    }
    
    
    配置文件,组织两者的关系
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
    
    
        <bean name="s" class="service.ProductService"/>
    
        <bean id="loggerAspect" class="AOP.LoggerAspect"/>
    
        <aop:config>
            <aop:pointcut id="loggerCutpoint"
                          expression=
                                  "execution(* service.ProductService.*(..)) "/>
    
            <aop:aspect id="logAspect" ref="loggerAspect">
                <aop:around pointcut-ref="loggerCutpoint" method="log"/>
            </aop:aspect>
        </aop:config>
    
    </beans>
    View Code

    SpringAOP 注解实现

     @Aspect 注解表示这是一个切面
    @Component 表示这是一个bean,由Spring进行管理

    @Around(value = "execution(* service.ProductService.*(..))") 表示对service.ProductService 这个类所有方法吧??

    使用@Component("s") 注解ProductService 类,通过s获得这个类

    <context:component-scan base-package="aspect"/>

    <context:component-scan base-package="service"/>

    扫描包com.how2java.aspect和com.how2java.service,定位业务类和切面类

    <aop:aspectj-autoproxy/> 

    找到被注解了的切面类,进行切面配置。。。。。这个是和动态代理有关得吧

    代码

    切面
    @Aspect
    @Component   spring管理
    public class LoggerAspect {
        切面执行哪里的切点,以around方式执行
        @Around(value = "execution(* service.ProductService.*(..))")
        public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("start log:" + joinPoint.getSignature().getName());
            Object object = joinPoint.proceed();
            System.out.println("end log:" + joinPoint.getSignature().getName());
            return object;
        }
    }
    
    
    核心业务    @Component 表示这是一个bean,由Spring进行管理
    @Component("s")
    public class ProductService {
        public void doSomeService(){
            System.out.println("核心业务");
        }
    } 
    
    
    调用业务代码
    public class TestAOPAnno {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext(
                    new String[] { "applicationContext.xml" });
            ProductService s = (ProductService) context.getBean("s");
            s.doSomeService();
        }
    }
    
    
    配置文件  扫描+动态代理
    <context:component-scan base-package="aspect"/>
        <context:component-scan base-package="service"/>
        <aop:aspectj-autoproxy/>   
    View Code
  • 相关阅读:
    《新土改:土地制度改革焦点难点辨析》:土地涨价要归公并用于城市配套设施,城市化的主角是人,小产权房不应该合法化,四星
    《清明上河图密码》:北宋的福尔摩斯探案,五星
    《中国的人口与城市》:关于中国人口与中国城市的数据分析,4星推荐。
    《集体失忆的黑暗时代》:已故加拿大公共知识分子关于城市规划与人类文明的随笔,三星推荐
    《智慧社会:大数据与社会物理学》:研究人类的想法的流动扩散的规律,四星
    《中国东部三大都市圈城市体系演化机制研究》:博士论文,结论是北上广深城市化规模还是不够,三星推荐
    《中国十亿城民——人类历史上最大规模人口流动背后的故事》:中国城市化将继续,城市对外来务工者应该更友好更包容,三星
    [Javascript] JavaScript赋值时的传值与传址
    [Vue @Component] Pass Props Between Components with Vue Slot Scope & renderless component
    [Dart] Understand Variables and Constants in Dart
  • 原文地址:https://www.cnblogs.com/woainixxx/p/11121557.html
Copyright © 2011-2022 走看看