吹牛了哈!网上有关DI和AOP的讲解数不胜数,多如牛毛。但你们站的位置好像有点高,没有顾及像我这种菜鸟,我通过通常恶补以后,终于大体弄明白DI和AOP是什么?决定将本身的一些想法写一下。php
这篇文章应该不会是最通俗的讲解,但我可以保证的是我将站在初学者的角度讲解,毕竟我也是初学者,可能更能体会初学者的无奈。本文只会告诉你们DI和AOP是什么,不会涉及怎么去运用。java
DI和AOP是JAVA后端框架Spring的核心,要想搞懂Spring,这个东西不弄懂,即使spring用得再好也枉然。简单的说,这二者都是一种编程思想,相似于五大编程原则之类的,但这两个思想确实挺颠覆。多说一句,我不建议新手一开始就直接上手springboot,直接接触springboot会有各类疑问,不少东西都是云里雾里,仍是得从spring上手,推荐一本spring书籍《spring实战》,这本书已经出到第五版了,真的是Java程序员居家旅行、必备之物。程序员
DI全名 Dependency Injection,中文译为依赖注入,它就是用来注入依赖的。先忘记这个,咱们来看一个场景,如今咱们打算用程序来记录学生的学习,学生有小学生、中学生和大学生,不一样级的学生学习的课程天然也不同。spring
传统的编咱们很天然地想创建至少两个类来进行实现,一个是学生的Student类,一个是用来描述学习的Learning类。别跟我说什么一个类就能够解决问题只须要将学习行为当作Student类的一个方法的话。咱们这里的学习是很复杂的行为,须要分不少科目,须要不少代码,这样子就不适合把全部代码都放在一个类,一个文件中了,毕竟在一个文件中写个上千行代码是很危险的事情(被接手的同事打)。不一样年级的学习行为很不同,这些咱们也不该该放到一块儿,因此应该有PrimaryStudentLearning、MiddleStudentLearning、CollegeStudentLearning等类。代码实现以下:express
package com.student.learning;
public class PrimaryLearning {
public void learning() {
System.out.println("小学生学习数学、语文、英语");
}
}
复制代码
package com.student.learning;
public class MiddleLearning {
public void learning() {
System.out.println("中学生学习物理、生物、化学");
}
}
复制代码
package com.student.learning;
public class CollegeLearning {
public void learning() {
System.out.println("大学生学习高数、毛概、线代");
}
}
复制代码
package com.student.learning;
public class Student {
// 1表示小学生,二、中学生 三、大学生
private int grade;
public Student(int grade) {
this.grade = grade;
}
public void doLearning() {
if (grade == 1) {
PrimaryLearning pl = new PrimaryLearning();
pl.learning();
} else if(grade ==2) {
MiddleLearning ml = new MiddleLearning();
ml.learning();
} else {
CollegeLearning cl = new CollegeLearning();
cl.learning();
}
}
}
复制代码
package com.student.learning;
public class Main {
public static void main(String[] args) {
Student student = new Student(2);
student.doLearning();
}
}
复制代码
各个类职责分明,貌似没什么问题,但仔细一想Student类引入了三个学习的类,若是下一步来了一个统计运动时间的需求,是要继续增长吗?咱们必须明白一个关联不少组件的组件,它的可维护性、可扩展性、健壮性都将变得极低,它会变得极难测试。“高内聚,低耦合”的组件设计思想先后端是通用的。新手若是不理解,那就记得:一个组件或类关联其它部分越少越好,可是又是不可能彻底没有关联的,由于各部分只有协同才能完成总体功能。编程
好了!上面欣赏了一波传统的编程思路,接下来咱们看看使用DI是怎样写的:后端
package com.student.learning;
public interface Learning {
void learning();
}
复制代码
package com.student.learning;
public class PrimaryLearning implements Learning{
@Override
public void learning() {
System.out.println("小学生学习数学、语文、英语");
}
}
复制代码
package com.student.learning;
public class MiddleLearning implements Learning {
@Override
public void learning() {
System.out.println("中学生学习物理、生物、化学");
}
}
复制代码
package com.student.learning;
public class CollegeLearning implements Learning {
@Override
public void learning() {
System.out.println("大学生学习高数、毛概、线代");
}
}
复制代码
package com.student.learning;
public class Student {
// 1表示小学生,二、中学生 三、大学生
private int grade;
private Learning l;
public Student(int grade, Learning learning) {
this.grade = grade;
this.l = learning;
}
public void doLearning() {
this.l.learning();
}
}
复制代码
package com.student.learning;
public class Main {
public static void main(String[] args) {
Learning learning = new CollegeLearning();
Student student = new Student(2, learning);
student.doLearning();
}
}
复制代码
能够看到咱们先是定义了一个Learning的接口,接着另外三个learning类都实现了这个接口,而后在Student类中使用的时候,咱们再也不直接将另外三个类引入,而是经过Student引入一个Learning类型的实例,这样子你若是传入PrimaryLearning的实例,我便执行小学生的学习方法,如此达到了你传入什么实例我便执行什么方法,Student完成不须要知道你传入的是什么,将会作什么事,它只关注自身便可,有点关注点分离的意思。如今整个Student类只引入了一个接口,而接口只是一种规范,跟具体的业务无关,这极大地下降了代码的耦合。这即是spring依赖注入的其中一种方法——构造函数注入。依赖经过构造函数的参数传入,只需引入一个接口。springboot
AOP全名Aspect Oriented Programming,中文为面向切面编程。我只听过面向对象编程,面向切面编程是什么鬼?这应该是大部分人的想法了。这个思想比DI给我冲击更大,它让你把遍及各处的业务提取出来造成可重用组件。啥?这句话什么意思?抱歉!我也不懂。bash
好了,接着DI那个例子,假如如今又有了一个新的需求,须要记录每一个学生的学习时间。嗯!我有上中下三策。框架
下策:直接在Student中加入这个功能,在学习前记录一下当前时间,在学习完成后又记录一下当前时间,这二者相减获得学习时间。这样把不属于Student这个类的业务也放在了Student中,当只有几行代码时,问题彷佛不大,但当这个时间统计业务代码量很大的时候,Student这个会变得极其复杂,再有一点,若是其它业务也须要记录时间的时候,难道我要每个地方都要写一个。
中策:我会将时间统计业务抽离出来,搞一个可复用的组件。使用这个组件我能够完成时间记录功能。代码以下:
package com.student.learning;
import java.util.Date;
public class RecordingTime {
private long startTime;
private long endTime;
public void recordStartTime() {
startTime = new Date().getTime();
}
public void recordEndTime() {
endTime = new Date().getTime();
System.out.println("学习了"+ (endTime - startTime) +"ms");
}
}
复制代码
package com.student.learning;
public class Student {
// 1表示小学生,二、中学生 三、大学生
private int grade;
private Learning l;
public Student(int grade, Learning learning) {
this.grade = grade;
this.l = learning;
}
public void doLearning() {
RecordingTime rt = new RecordingTime();
rt.recordStartTime();
try {
Thread.currentThread().sleep(3000);
this.l.learning();
rt.recordEndTime();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
上面我将记录时间的代码抽离为一个RecordingTime类,但即使我将这个业务抽离为一个组件,我仍是须要在Student中调用。这个记录学习时间的事情不该该是学生作才对,学生也不该该知道这件事情,毕竟你们都会摸鱼和划水。那有没有什么方法在学生不知道的状况下作了这件事情呢?
上策:利用AOP是彻底能够在学生不知情的状况下完成这件事情。实现一个AOP库固然能够,奈何实力不够,但能够直接使用spring,下面看看spring是如何使用AOP达到业务彻底分离的。直接亮代码:
public class Student {
// 1表示小学生,二、中学生 三、大学生
private int grade;
private Learning l;
public Student(int grade, Learning learning) {
this.grade = grade;
this.l = learning;
}
public void doLearning() {
try {
Thread.currentThread().sleep(3000);
this.l.learning();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="Student" class="com.student.learning.Student">
<constructor-arg value="3"/>
<constructor-arg ref="CollegeStudent" />
</bean>
<bean id="CollegeStudent" class="com.student.learning.CollegeLearning"/>
<bean id="PrimaryStudent" class="com.student.learning.CollegeLearning"/>
<bean id="MiddleStudent" class="com.student.learning.CollegeLearning"/>
<bean id="RecordingTime" class="com.student.learning.RecordingTime" />
<aop:config>
<aop:aspect ref="RecordingTime">
<aop:pointcut id="learning" expression="execution(* *.doLearning(..))" />
<aop:before method="recordStartTime" pointcut-ref="learning" />
<aop:after method="recordEndTime" pointcut-ref="learning" />
</aop:aspect>
</aop:config>
</beans>
复制代码
package com.student.learning;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/student/learning/student.xml");
Student student = context.getBean(Student.class);
student.doLearning();
}
}
复制代码
记录开始学习时间点:Thu Nov 28 17:00:36 CST 2019
大学生学习高数、毛概、线代
学习了3029ms
复制代码
上面的Student类彻底没有去调用RecordTime的方法,它对时间记录业务是无感知的。那到底是怎样作到的呢?核心是student.xml配置文件,咱们把Student、ColldgeLearning这些类都经过bean声明在配置文件中,每个bean都有一个ID,这里我直接将类名看成ID。在bean声明以后,还声明了aop配置,这一块咱们逐行解读:
<bean id="RecordingTime" class="com.student.learning.RecordingTime" />
<aop:config> //声明一个aop配置
// 声明一个aop切面,这个面引用RecordingTime bean,也能够这个切面的上下文就是RecordingTime
<aop:aspect ref="RecordingTime">
// 这里定义一个切面点,这个点的id是learning, expression关于这个切面点的描述,
// execution(* *.doLearning(..)) 表示当切面点是doLearning方法执行的时候
<aop:pointcut id="learning" expression="execution(* *.doLearning(..))" />
// before表示在切面点及doLearning执行以前调用 RecordingTime的recordStartTime方法
<aop:before method="recordStartTime" pointcut-ref="learning" />
// before表示在切面点及doLearning执行以后调用 RecordingTime的recordEndTime方法
<aop:after method="recordEndTime" pointcut-ref="learning" />
</aop:aspect>
</aop:config>
复制代码
而在程序入口main函数中,经过ClassPathXmlApplicationContext直接加载解析这个文件,这样能够拿到全部声明的类,在须要的时候建立对应的实例,好比context.getBean(Student.class);就会建立一个Student的实例返回。经过xml配置文件声明bean和aop只是spring的一种配置方法,目前比较流行的是经过注解进行声明。
看到了这里,你们对于AOP是什么应该有一个大体的印象?咱们能够将其看作第三方,一方和另一方若是想没有关联,那么必须存在一个第三方来协调二者。spring对于咱们的类就是一个第三方,一个类要想在和其它类没有关联的状况完成协做,那么必然须要第三方协调这二者。只不过spring的权利有点大,它全权负责了咱们实例的建立、使用和管理。
DI和AOP本质上都是一种用以下降各个组件耦合性的设计,以松散的耦合组织代码,提升各个组件的独立性,使各个组件更易于测试。编程走过了几十年的路,我认为AOP和DI是一个很大的突破,至少对于JAVA是这样的。若是你尚未意识到解耦的重要性,那么尽管去写代码吧!终有一天你接手了一份如千丝万缕杂糅在一块儿的代码时,你便会清醒意识到。解耦,解耦,解耦!