springMVC+maven+mybatis+mysql入门

(三)DI,IOC,AOP等基本概念

本文简单讲解一下DI,IOC,AOP,spring的基本理念就是这几个,想要用好springMVC,多少需要了解一些。本文主要参考《Spring实战》(第四版)

首先想说的是,这年头国外的那些开发人员,开始有点走上邪教的道路,不知道是为了体现技术的独特,还是为了让别人不太容易懂,喜欢取出各种各样稀奇古怪的名字,加上中英文翻译的问题,中文名字看起来就更玄了。

不要拘泥于对概念名称的理解,只要能理解概念的具体含义就可以了。


1.准备工作
根据上一篇 (二)创建maven工程 创建好一个Maven工程,我们叫做 DIdemo ,创建好之后,目录结构大致如下:

目录结构.png

jdk版本不用在意,每个人的不同
打开com.springdemo.DIdemo下的App.java,里面已经写好了一个main函数,我们改一改打印输出

package com.springdemo.DIdemo;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!Wa ha ha!!" );
    }
}

在代码区,点右键,找到 Run As,选择 Java Application

run.png

如果Myeclipse配置没有问题的话,在下边功能窗口中,Console一栏,会输出main函数中我们打印的 "Hello World!Wa ha ha!!"
这样就表明,我们的工程建立成功。
然后,修改pom.xml文件,添加spring依赖包

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.springdemo</groupId>
  <artifactId>DIdemo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>DIdemo</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>5.0.7.RELEASE</spring.version>
  </properties>

 <dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <!-- spring start -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${spring.version}</version>
    </dependency>
    

    <!-- spring end -->
    
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.1</version>
    </dependency>
    

  </dependencies>
</project>

只需要关注 <dependencies></dependencies> 之间的依赖包配置即可,其他部分根据不同版本的maven,不同版本的Myeclipse,都会有不同,不用在意。

2.依赖注入
依赖注入DI(Dependency Injection) 要说清这个概念,靠文字理论来说明,不直观,不好理解,我们直接用代码来说明。
我们这里举一个例子,可能不是最适合的,但是基本可以说清这个概念。

首先,我们创建学生,学生的一个任务就是完成作业...

package com.springdemo.DIdemo;

public interface Student {
    void finishHomeWork(); 
}

所以,我们还得有作业

package com.springdemo.DIdemo;

public interface Homework {
    
    void doHomework();
}

好啦,假如我们这里有个一年级学生

package com.springdemo.DIdemo;

public class GradeOneStudent implements Student {   
    
    public void finishHomeWork() {      

    }

}

然后,这个学生有数学作业

package com.springdemo.DIdemo;

public class MathHomework implements Homework {

    public void doHomework() {
        System.out.println("Wa ha ha,the math homework is finished!!");
    }

}

现在,我们要让这个一年级的学生,做数学作业,早些时候的实现方式是这样的:

package com.springdemo.DIdemo;

public class GradeOneStudent implements Student {   
    
    private MathHomework todayMathWork;
    
    public GradeOneStudent(){
        this.todayMathWork = new MathHomework();
    }
    
    public void finishHomeWork() {      
        todayMathWork.doHomework();
    }

}

然后我们在main函数中让学生做作业

package com.springdemo.DIdemo;

public class App 
{
    public static void main( String[] args )
    {
        GradeOneStudent oneStudent = new GradeOneStudent();
        oneStudent.finishHomeWork();
    }
}

这是早些时候比较常见的用法,需要用那个类,就创建一个类的对象,然后使用它。
学生和作业之家,是一个依赖关系。
在功能上,这样写没有问题,而且我们平时可能也写了不少这样的代码,但是,这样写,学生作业就成了一个紧密耦合关系,这样不利于类的扩展,维护等,例如,现在小学生要做语文作业了,我们只能修改GradeOneStudent,在里面创建一个语文作业的对象,然后使用。
为了解决这个问题,我们怎么办呢,修改一下GradeOneStudent

package com.springdemo.DIdemo;

public class GradeOneStudent implements Student {   
    
    private Homework todayWork;
    
    public GradeOneStudent(Homework todayWork){
        this.todayWork = todayWork;
    }
    
    public void finishHomeWork() {      
        todayWork.doHomework();
    }

}

修改后,我们没有自己创建作业,而是在构造器中将作业做为构造器参数传入进来,这就是 依赖注入,这是依赖注入的一种方式,构造器注入。
这样修改之后,我们可以完成各种作业,在main中,传入作业对象即可。

package com.springdemo.DIdemo;

public class App 
{
    public static void main( String[] args )
    {
        MathHomework todayMathWork = new MathHomework();
        GradeOneStudent oneStudent = new GradeOneStudent(todayMathWork);
        oneStudent.finishHomeWork();
    }
}

这样,学生作业之间,就是一个松耦合关系,这就是依赖注入的一个主要目的。
通过上面的讲解,可以简单理解依赖注入的意思了。
3.控制反转
控制反转(Inversion of Control,IOC)从字面上理解,就是把控制反转了,正常的控制是怎么样的呢?就是我们上面的例子,学生要写作业,在main中创建一个作业,注入给学生,这个过程,是我们自己控制的。那么把控制反转了,在代码里怎么体现呢?
这个就是Spring的主要理念了,让我们把控制交给它,我们自己不管,在需要的时候,Spring自动把需要的对象注入进来,这就是控制反转了。
继续用上面的代码作为示例。
这里,我们增加Spring配置文件,在 com.springdemo.DIdemo 包下面创建一个 student.xml(放在这个位置不严谨,但是方便使用和说明),这样我们的工程目录结构大致是这样的

目录结构.png

student.xml的内容如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="student" class="com.springdemo.DIdemo.GradeOneStudent">
    <constructor-arg ref="homework" />
  </bean>

  <bean id="homework" class="com.springdemo.DIdemo.MathHomework">
  </bean>

</beans>

其中有两段是关键配置:

  <bean id="student" class="com.springdemo.DIdemo.GradeOneStudent">
    <constructor-arg ref="homework" />
  </bean>

  <bean id="homework" class="com.springdemo.DIdemo.MathHomework">
  </bean>

首先,java Bean这个概念,我自己理解就是可以重用的一个组件,更深刻的理解还需要读者自行研究。 这个配置理解起来也比较简单, bean id 类似于这个bean的名字,后面别的bean使用的时候,直接用这个名字,这里,我们配置了两个bean,一个是我们的GradeOneStudent,一个是MathHomework,其中GradeOneStudent 多了一个配置

<constructor-arg ref="homework" />` 

这个 constructor-arg配置的就是构造器参数,ref="homework"就是指,将 homework这个bean作为参数传进GradeOneStudent的构造器,而我们的GradeOneStudent的构造器确实需要一个homework参数

    public GradeOneStudent(Homework todayWork){
        this.todayWork = todayWork;
    }

student.xml文件写好后,我们修改main函数:

package com.springdemo.DIdemo;

import org.springframework.context.support.ClassPathXmlApplicationContext;



public class App 
{
    public static void main( String[] args )
    {
        ClassPathXmlApplicationContext context = 
                new ClassPathXmlApplicationContext("com/springdemo/DIdemo/student.xml");
            Student gradeOneStudent = context.getBean(Student.class);
            gradeOneStudent.finishHomeWork();
            context.close();
    }
}

通过Spring框架的ClassPathXmlApplicationContextstudent.xml加载进去,它会自动识别bean的配置,这样,我们就可以获取到Student bean,直接执行finishHomeWork(),Spring会自动注入我们需要的依赖。
这个由Spring来控制在需要的时候注入依赖的过程,就是控制反转IOC


这里增加一些说明,我们上面的示例里面,在配置文件里,只配置了一个Student类型的bean,所以,我们这样写

Student gradeOneStudent = context.getBean(Student.class);

可以直接找到我们定义的GradeOneStudent bean,但是,如果有多个Student类型的bean,那么就需要我们自己指定bean的id来获取了。例如,我们创建一个二年级学生

package com.springdemo.DIdemo;

public class GradeTwoStudent implements Student {

    private Homework todayWork;
    public GradeTwoStudent(Homework todayWork){
        this.todayWork = todayWork;
        
    }
    public void finishHomeWork() {
        System.out.println("I am grade Two");
        todayWork.doHomework();
    }

}

student.xml中配置好这个bean

<bean id="studentOne" class="com.springdemo.DIdemo.GradeOneStudent">
    <constructor-arg ref="homework" />
  </bean>
  
  <bean id="studentTwo" class="com.springdemo.DIdemo.GradeTwoStudent">
    <constructor-arg ref="homework" />
  </bean>

  <bean id="homework" class="com.springdemo.DIdemo.MathHomework">
  </bean>

这样,我们就有两个Student类型的bean了,如果main还是按照之前的方式执行,会报错找不到具体的bean

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.springdemo.DIdemo.Student' available: expected single matching bean but found 2: studentOne,studentTwo

这种情况,需要我们在加载bean的时候,指定bean id

package com.springdemo.DIdemo;

import org.springframework.context.support.ClassPathXmlApplicationContext;



public class App 
{
    public static void main( String[] args )
    {
        ClassPathXmlApplicationContext context = 
                new ClassPathXmlApplicationContext("com/springdemo/DIdemo/student.xml");
            Student gradeOneStudent = context.getBean("studentOne",Student.class);///这里,需要指定bean id
            gradeOneStudent.finishHomeWork();;
            context.close();
    }
}

关于spring框架下的bean的其他相关问题,后面我会单独写一篇文章。


4.AOP
面向切面编程(Aspect Oriented Programming ,AOP)在我的理解,就是基于依赖注入DI的一种编程方式,利用DI这种概念来实现减轻耦合,降低重复代码量,便于coder维护代码的一种编程方式。
讲一堆概念,还是不如来个栗子。

还是我们前面学生写作业的工程。
大部分小盆友读一年级的时候,还需要家长的鼓励支持,开始写作业的时候,妈妈会表扬一下小盆友的自觉性,作业做完了,再表扬一下,以此增加小盆友写作业的积极性,我们来模拟这个场景。
先增加一个妈妈类。

package com.springdemo.DIdemo;

public class Mather {
    public void beforeHomework(){
        System.out.println("Good!! that's great.");
    }
    
    public void afterHomework(){
        System.out.println("Nice work!!!");
    }
}


然后,按照上面依赖注入的方式,加入到GradeOneStudent

package com.springdemo.DIdemo;

public class GradeOneStudent implements Student {   
    
    private Homework todayWork;
    private Mather myMather;
    
    public GradeOneStudent(Homework todayWork,Mather myMather){
        this.todayWork= todayWork;
        this.myMather = myMather;
    }
    
    public void finishHomeWork() {  
        myMather.beforeHomework();
        todayWork.doHomework();
        myMather.afterHomework();
    }

}

最后,到student.xml中进行配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="studentOne" class="com.springdemo.DIdemo.GradeOneStudent">
    <constructor-arg ref="homework" />
    <constructor-arg ref="mom" />
  </bean> 

  <bean id="mom" class="com.springdemo.DIdemo.Mather">
  </bean>

  <bean id="homework" class="com.springdemo.DIdemo.MathHomework">
  </bean>

</beans>

这样,我们就可以在main中运行啦。
但是。。。但是。。。有点不太对的地方,从代码语义上来看,妈妈由学生来控制了,写作业这个方法中,不仅写了作业,还去找了妈妈两次,这个就不太合适了。
这里,我们引入切面的概念,妈妈类作为一个切面,切点就是学生做作业,在做作业前后,切面切入进来,执行它的动作。我们如何来实现呢?
修改一下student.xml配置文件

<?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"
  xsi:schemaLocation="http://www.springframework.org/schema/aop 
      http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="studentOne" class="com.springdemo.DIdemo.GradeOneStudent">
    <constructor-arg ref="homework" />
  </bean> 

  <bean id="mom" class="com.springdemo.DIdemo.Mather">
  </bean>

  <bean id="homework" class="com.springdemo.DIdemo.MathHomework">
  </bean>
  
  <aop:config>
    <aop:aspect ref="mom">
      <aop:pointcut id="doHomework"
          expression="execution(* *.finishHomeWork(..))"/>
        
      <aop:before pointcut-ref="doHomework" 
          method="beforeHomework"/>

      <aop:after pointcut-ref="doHomework" 
          method="afterHomework"/>
    </aop:aspect>
  </aop:config>


</beans>

注意,我们这里引入了<aop>标签,所以xml开头的beans声明有变动。
我们这里增加了一段aop的配置,<aop:aspect>引入了妈妈类的beanmom,然后通过<aop:pointcut>定义了一个切点执行finishHomeWork的时候,最后通过<aop:before>声明了前置通知,<aop:after>声明了后置通知。这部分是采用的AspectJ的切点表达式语言。
接着,我们就可以把学生类中调用妈妈的方法去掉。

package com.springdemo.DIdemo;

public class GradeOneStudent implements Student {   
    
    private Homework todayWork;
    
    public GradeOneStudent(Homework todayWork,Mather myMather){
        this.todayWork= todayWork;      
    }
    
    public void finishHomeWork() {  
        todayWork.doHomework();

    }

}

在main中运行一下,就可以实现前面学生自己调用妈妈方法的效果。
这个简单是示例,基本体现了AOP的编程思想。

对于上面这些基本概念的简单理解,有助于我们后面Spring MVC的开发过程中,对于各种配置操作的理解,不至于懵懵懂懂死记配置。

上面使用的工程源码,SpringStart github仓库 DIdemo,请自行下载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容