参考:
https://www.cnblogs.com/flowwind/p/4782606.html
https://blog.csdn.net/qq_32317661/article/details/82878679
https://blog.csdn.net/weianluo/article/details/81607134
https://www.bilibili.com/video/BV1w3411s7ur?p=1&vd_source=8e2814dcf7dcedfba24d7669d5651c92
一、IOC:控制反转
https://www.jianshu.com/p/c354c439facd
1、IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题,A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。
2、DI(依赖注入)
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
二、AOP:面向切面编程
AOP可以将那些与业务无关,却为业务模块所共同调用的逻辑或责任。比如事务处理、日志管理、权限控制等。封装起来,便于降低系统的反复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
举例说明(详见参考链接):
有A,B,C三个方法,在调用每一个方法之前,要求打印一个日志:某一个方法被开始调用了!在调用每个方法之后,也要求打印日志:某个方法被调用完了!
一般人会在每一个方法的开始和结尾部分都添加一句日志打印,这样做如果方法多了,就会有很多重复的代码,这时候有人会想到,为什么不把打印日志这个功能封装一下,然后让它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用呢?所以AOP就是做了这一类的工作。
把和主业务无关的事情,放到代码外面去做。
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
常用应用场景:日志应用,权限控制,缓存(比如实现对Redis的缓存同步),异常处理等
AOP主要包含了通知、切点和连接点等术语,描述如下:
1.通知(advice)
它用于描述切面的目标即切面必须要完成的工作。指拦截到连接点之后要执行的代码。通知定义了切面是什么以及何时调用,何时调用包含以下5种:
SpringAOP可以应用5种类型的通知:
(1)前置通知(Before):在目标方法被调用之前调用通知功能。
(2)后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
(3)返回通知(After-returning):在目标方法成功执行之后调用通知。
(4)异常通知(After-throwing):在目标方法抛出异常后调用通知。
(5)环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
2.切点(pointcut)
通知定义了干什么和何时,切点定义了何处,切点的定义会匹配通知所要织入的一个或多个连接点,我们通常使用明确的类的方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称来指定这些切点。
切点的格式如下:
execution(* * cn.com.spring.service.impl.*.*(..))
1)第一个* 表示所有的修饰符(public、private等)
2)第二个* 表示所有的返回类型
3)中间是包名
3)第三个* 代表包内所有的类名
4)第四个* 代表所有的方法名
5)最后的…代表所有的参数名
3.连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象。可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。
JoinPoint接口主要方法有:
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
Signature getSignature() :获取连接点的方法签名对象;
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object getThis() :获取代理对象本身;
4.织入(weave)
将切面应用到目标对象并导致代理对象创建的过程(生成代理的过程)
二、AOP简单实现(以打印日志为例):
1.首先在pom.xml中添加aspectJ依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
2.新建一个包aspect,新建一个类LogAspect
@Aspect //使之成为切面类
@Component //把切面类注入到IOC容器中
public class LogAspect {
private static final String OPERATION_LOG_NAME = "operationLog"; //日志名
private static final String LOG_FORMATTER = "方法调用前:%s.%s - %s";
private static final String LOG_FORMATTER_AFTRE = "方法调用后:%s.%s-%s, return - %s";
Logger log = Logger.getLogger(OPERATION_LOG_NAME);
@Pointcut("execution(* com.news.news.controller.*.*(..))") //处理com.news.news.controller包内所有类的所有方法
public void all(){ //此方法名相当于切点的名字
}
//前置通知:目标方法执行之前执行以下方法体的内容
@Before(value="all()")
public void allMethodBefore(JoinPoint joinPoint){
String className = joinPoint.getTarget().getClass().getName();//拿到类名
Signature signature = joinPoint.getSignature(); // 拿到方法签名
String methodName = signature.getName();//拿到方法名
Object[] args = joinPoint.getArgs();//拿到参数名
log.info(String.format(LOG_FORMATTER, className, methodName, Arrays.toString(args)));
}
//返回通知:目标方法正常执行完毕时执行以下代码
@AfterReturning(value="all()", returning = "res") //拿到方法返回值并取名为res
public void allMethodAfter(JoinPoint joinPoint, Object res){
String className = joinPoint.getTarget().getClass().getName();//拿到类名
Signature signature = joinPoint.getSignature();// 拿到方法签名
String methodName = signature.getName();//拿到方法名
Object[] args = joinPoint.getArgs();//拿到参数名
log.info(String.format(LOG_FORMATTER_AFTRE, className, methodName, Arrays.toString(args), res.toString()));
}
/*
也可以继续添加
//后置通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。
@After(value="all()")
//异常通知:目标方法发生异常的时候执行以下代码
@AfterThrowing(value="all()",throwing="e")
public void afterThorwingMethod(JoinPoint jp, NullPointerException e){
}
*/
}