(三)DI,IOC,AOP等基本概念
本文简单讲解一下DI,IOC,AOP,spring的基本理念就是这几个,想要用好springMVC,多少需要了解一些。本文主要参考《Spring实战》(第四版)
首先想说的是,这年头国外的那些开发人员,开始有点走上邪教的道路,不知道是为了体现技术的独特,还是为了让别人不太容易懂,喜欢取出各种各样稀奇古怪的名字,加上中英文翻译的问题,中文名字看起来就更玄了。
不要拘泥于对概念名称的理解,只要能理解概念的具体含义就可以了。
1.准备工作
根据上一篇 (二)创建maven工程 创建好一个Maven工程,我们叫做 DIdemo ,创建好之后,目录结构大致如下:
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
如果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(放在这个位置不严谨,但是方便使用和说明),这样我们的工程目录结构大致是这样的
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框架的ClassPathXmlApplicationContext
把student.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,请自行下载。
网友评论