spring-IoC-AOP

spring-IoC/DI

IoC(Inversion of Control)控制反转,IoC不是一种技术,本身是一种思想。想要理解IoC,首先需要理解两个词,Control(控制)和Inversion(反转)。

Control(控制)

什么是控制?以代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class BookController {

//@AutoWired
private BookService bookService;
public BookController(){
this.bookService = new BookService();
}

@ResponseBody
@RequestMapping(value = "bookIds",method = RequestMethod.GET)
public List<Book> bookIds(){
List<Book> list = bookService.fetchAll();
return list;
}
}

在这段代码中,BookController需要用到BookService,因此在前者中有一个后者的实例,换句话说,BookController可以通过这个实例控制BookService,这就是控制。而从另一个角度来说,也是BookController依赖于BookService(因为没有BookService,BookController的逻辑也不能实现)。

控制和依赖是共存的,控制也不局限于对另一个类的控制,还可以是文件,datasource等。

Inversion(反转)

什么是反转?我们先考虑什么是“正转”?
注意无论是“正转”还是反转,都是相对于BookController类来说的
还是以上面的代码为例,上面的代码中,BookController是占主要的地位的类,BookService只是BookController的一部分,由BookController控制BookService,这可以理解为“正转”,因为这是占主导地位的类控制占次要地位的类(也可以理解为依赖类控制被依赖的类),BookController主动new一个BookService的实例。

那么反转呢?反转就是在BookController和BookService中,BookController由原来的主动new一个对象,变为被动接受一个对象。就好像说,有一个第三方,在BookController需要一个BookService的时候,马上送上一个BookService。而这个第三方,就是spring容器。

DI(Dependency Injection)

依赖注入其实就是IoC的另一种说法。我们还是从两个方面来说什么是依赖注入。
什么是依赖:IoC控制的另一种说法,可以参考第一点
什么是注入:IoC容器给BookController送上一个BookService实例即可以视为注入。

AOP(Aspect-Oriented Programming)

概念理解,和OOP对比。

AOP面向切面编程。面向对象编程的核心是对象,一切皆对象,同样的,面向切面编程,核心是切面。想要理解什么是AOP,首先需要理解切面。

什么是切面?

OOP要求我们把功能分散到单独的类中去,做到一个类做一件事。但是当许多类都分散开来的时候,也会增加类的复杂性。一个例子就是:如果所有的类都有一个打印日志的操作,要么在每个类中重复一段打印日志的代码(重复代码);要不就是所有需要打印日志的类都依赖于一个log类(会增加所有这些类对于log类的依赖,重复逻辑)。如何才能去掉这些重复代码呢?AOP就是用来解决这个问题的,将log的方法视作一个切面,所有log操作只需要通过AOP织入即可,代码只需要写一份。
切面就是上面的log

所谓的面向切面编程,就是根据切面,在代码的适当位置(切入点)动态的插入适当的代码让其运行。

AOP:如果你有一个函数要测时间,那你就在函数开始和结束的地方各打一个时间戳,然后计算打印就行了。但如果你有一批函数,你要每个函数都这么干的话,明天老板说该上线了把debug代码都去掉怎么办,明天CTO说控制台输出太low了要改成发短信怎么办。所以你需要在一个统一的地方写这些东西。AOP就是通过配置,让符合某种条件的函数在开始和结束的时候都执行你规定的逻辑。 –摘抄自知乎

AOP理解

1
2
3
4
Aspect(切面):从所有类中横向找出的共同逻辑
Join point(连接点):从程序中找出的可以拦截点(spring中只可以是method,不能是构造器)
Advice: 需要动态插入的逻辑代码(有after,before,around等多种)
Pointcut: 切入点,可以理解为找出来的特定的连接点,Advice在只能在Pointcut中被插入。

示例

ExampleController类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.demo.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Example {

@RequestMapping("/")
public String home() {
return "Hello World!";
}
}

Aspect类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.example.demo.Aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Aspect
@Component
public class WebLogAspect {
protected static org.slf4j.Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

// @Pointcut("execution(public * com.example.demo..*.*(..))")
@Pointcut("execution(public * com.example.demo.web.Example.home(..))")
public void webLog() {
}

@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
System.out.println( "进入doBefore切面");
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

// 记录下请求内容
logger.info("URL : " + request.getRequestURL().toString());
logger.info("HTTP_METHOD : " + request.getMethod());
logger.info("IP : " + request.getRemoteAddr());
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));

}

@Around("webLog()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println( "around the aspect");
Object o = joinPoint.proceed();
System.out.println( "around the aspect1");
return o;
}

@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info("RESPONSE : " + ret);
}
}

输出:

1
2
3
4
5
6
7
8
around the aspect
进入doBefore切面
2018-03-07 16:23:54.237 INFO 22622 --- [nio-8888-exec-1] com.example.demo.Aop.WebLogAspect : URL : http://localhost:8888/
2018-03-07 16:23:54.238 INFO 22622 --- [nio-8888-exec-1] com.example.demo.Aop.WebLogAspect : HTTP_METHOD : GET
2018-03-07 16:23:54.238 INFO 22622 --- [nio-8888-exec-1] com.example.demo.Aop.WebLogAspect : IP : 127.0.0.1
2018-03-07 16:23:54.239 INFO 22622 --- [nio-8888-exec-1] com.example.demo.Aop.WebLogAspect : CLASS_METHOD : com.example.demo.web.Example.home
2018-03-07 16:23:54.239 INFO 22622 --- [nio-8888-exec-1] com.example.demo.Aop.WebLogAspect : ARGS : []
around the aspect1

可见执行顺序是:
Around的ProceedingJoinPoint.proceed()前的方法,Before方法,被织入的方法,Around的proceed之后的方法,After执行,然后是AfterReturing方法。

除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值。

当出现异常的时候,情况比较复杂,分有没有around方法讨论,因为如果有around则around当中理因处理该Exception,如果没有处理,则该exception会抛出到上一层,然后强行结束该around的advice,接着执行After方法,然后是afterthrowing方法。如果around执行了异常处理,那么afterthrow不会执行。

总结:around在before之前,after(所有类别after方法)之前执行。
afterreturing和afterthrowing在after之后执行。
[TODO poingcut的语句编写 AOP通过代理实现的原理]
详细用法参考

spring启动的顺序