Spring 入门之SpringAop与AspectJ
反思:
1.什么是spring的Ioc?为什么要设计Ioc?主要是解决什么问题?
2.什么是SpringAop?为甚要这么设计?主要解决什么问题?底层原理是什么?
3.事务的隔离级别有哪些?底层是怎么实现的?
4.静态代理和动态代理有什么区别?或者说他们都有哪些规范?
反思回答:
1.SpringIOC是一种控制反转的概念,主要是将创建对和依赖注入的控制交给Spring容器,由Spring容器管理的过程。
IoC 容器可以动态地注入依赖关系,使得更换实现类变得容易。如果需要更换某个组件的实现,只需修改容器配置,而不需要修改调用方的代码。这提高了代码的灵活性和可维护性
IoC 容器负责创建和配置组件,组件的调用方只需关注如何使用。这样可以避免重复创建对象,减少了资源浪费。例如,多个 Controller 可以复用同一个 Service,多个 Service 可以复用同一个 DAO。IoC 解决对象之间的耦合问题。
2.切面就是那些公共的,重复的,通用的逻辑功能(非主业务逻辑).AOP就是将切面单拎出来开发,在目标方法调用时自动反织回去的处理称为z面向切面编程
目的:解耦合,减少代码,让开发人员专注于业务的开发
底层实现:动态代理,jdk,cglib jdk 和cglib的动态代理的区别:
jdk需要实现接口 cglib不能是final
运行时生成的代理类,jdk之际额实现接口,而cglib继承目标类。
调用方式:jdk 反射 cglib 子类调用父类的方式
性能:jdk1.8 cglib 更好,反射的优点小于子类继承父类。后面相差无几
spring 默认采用jdk动态代理,而springboot默认采用cglib动态代理。
3.事务的隔离级别分为:
-
未提交读
允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
-
提交读
只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
-
可重复读
可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读
-
窜行读
完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
mysql的事务隔离级别是可重复读,Oracle的隔离级别是提交读。
事务隔离级别是如何实现的:
mysql5.1.3 采用的是互斥锁 --->导致并发量降低
采用的是MVCC解决方案不需要互斥锁,但是并不是所有的事务都是采用MVCC解决方案
读未提交就没有采用MVCC,读未提交是直接去读原数据,哪怕修改数据的事务还没提
交事务也会修改目标数据,所以不需要使用任何的锁技术。串行化采用表锁。
读已提交和可重复读这两种事务隔离级别采用MVCC设计模式。
MVCC如何实现:undolog.存储我们写操作的一些历史记录存储在undolog,每次修
改记录都会用一个链表连接起来。通过保存的事务id找到相关的修改的记录,这个事
务id存储在表的隐藏列当中DB_TRX_ID,DB_ROLL_PTR。
执行select语句时,会为查询的数据创建一个快照数据表(ReadView),通过快照数据
得知有无并行的其他事务也在操作同一条数据,如果有的话,就会去undolog找到事务
提交前或者提交后的数据,这样就可以不采用互斥锁以达到读已提交的事务隔离级别。
重复读,每次都会查询undolog数据,无论读多少次都是读取同一的数据。
4.静态代理:
1)代理对象在程序运行前就已经存在.
2)代理对象就是完成业务和切面的整合
3)代理对象必须与业务对象实现同一个业务接口(保证同一套业务)
4)静态代理实现中必须有业务接口
动态代理:
1)必须有业务接口
2)动态代理对象是在程序运行时构建的
3)动态代理对象是完成业务和切面功能整合的
SpringAop
原生SpringAop
1.常用以下通知
1)Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
2)After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
3)Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
4)Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。
2.Aop常见的术语
1)切面Aspect
重复的,通用的,公共的非主业务逻辑(交叉业务逻辑)称为切面.例如:日志,事务,权限等.
2)连接点JoinPoint
连接业务功能和切面功能的方法(目标方法).
3)切入点Pointcut
多个连接点构成切入点.切入点可以是一个方法,可以是一个类,可以是一个包.
4)目标对象Target
对谁操作,谁就是目标对象.ProductServiceImpl,BookServiceImpl等.
5)通知Advice
切入切面的时机是前切后切环绕切,出错切,什么时候切入.
3.案例代码
public class LogAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("\n[系统日志]"+sf.format(new Date())+"---"+method.getName()+ Arrays.toString(objects));
}
}
<!--创建业务对象-->
<bean id ="bookServiceTarget" class="com.dqw.service.impl.BookServiceImpl"></bean>
<!--创建切面的对象-->
<bean id="logAdvice" class="com.dqw.advice.LogAdvice"></bean>
<!--绑定业务和切面-->
<bean id="bookService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置业务接口-->
<property name="interfaces" value="com.dqw.service.BookService"></property>
<!--配置切面-->
<property name="interceptorNames">
<list>
<value>logAdvice</value>
</list>
</property>
<!--织入-->
<property name="target" ref="bookServiceTarget"></property>
</bean>
AspectJ
1.相关概念
Aspect是一个优秀的面向切面的框架,它扩展了Java语言提供了强大的切面实现。易学易用。
2.常见通知类型
1)@Before:前置通知,在目标方法调用前切入切面功能.
2)@AfterReturning:后置通知,在目标方法调用后切入切面功能.有可能改变目标方法的返回值.
3)@Around:环绕通知,通过拦截目标方法,在目标方法前后切入切面功能.
4)@After:最终通知,不管目标方法是否正常执行,切面功能都被切入.
5)@Pointcut:给切入点表达式起别名
AspectJ切点表达式
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
常用符号:
*代表任意字符
… 出现在路径中,代表当前路径及其子路径,及其路径下的所有类
出现在方法的参数中,代表任意参数
案例代码如下:
/*
* 环绕通知切面方法规范
* 1.访问权限是public
* 2.切面有返回值为目标方法返回值
* 3.切面方法自定义
* 4.切面方法有参数,参数就是目标方法本身
* 5.需要回避异常Throwable
* 6.使用@Around注解
* */
@Aspect
@Component
public class MyAspect {
// @Around(value = "myCut()")
// public void myAround(ProceedingJoinPoint pjp) throws Throwable {
// System.out.println("环绕通知前置——————————");
// //获取目标方法的传参
// Object[] args = pjp.getArgs();
// System.out.println("方法名:"+pjp.getSignature());
// System.out.println("参数:"+ Arrays.toString(args));
// Object proceed = pjp.proceed();
// System.out.println("环绕通知后置——————————");
// }
@Around(value = "myCut()")
public String myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知前置——————————");
//获取目标方法的传参
Object[] args = pjp.getArgs();
System.out.println("方法名:"+pjp.getSignature());
System.out.println("参数:"+ Arrays.toString(args));
Object proceed = pjp.proceed();
System.out.println("环绕通知后置——————————");
return "阿狸巴拉";
}
/*
* 最终通知:
* 主要用来做善后处理
* 规范:
* 1.访问权限是public
* 2.无返回值
* 3.切面方法自定义
* 4.切面方法无参数,如果有是JoinPoint
*
* */
@After(value = "myCut()")
public void myAfter(){
System.out.println("最终处理");
}
/*
* 后置通知切面的规范
* 1.访问权限是public
* 2.切面方法没有返回值
* 3.切面方法名任意
* 4.一般没有传参,如果有则是业务方法的返回值
* 用AfterReturning 注解声明
* 参数:
* value 切点表达式
* returning 设置参数名称,与切面方法的参数名要一致
*
* */
@AfterReturning(value = "myCut()",returning = "obj")
public void myAfterReturning(Object obj){
System.out.println("后置通知!!!");
}
@Before(value = "myCut()")
public void myBefore(JoinPoint jp){
System.out.println("前置通知");
}
// 简化切点表达式
@Pointcut(value = "execution(* com.dqw.s03.*.*(..))")
public void myCut(){}
}
<context:component-scan base-package="com.dqw.s02"/>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
修改代理为cglib
<context:component-scan base-package="com.dqw.s02"/>
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
对于aop必须重点关注的是通知和切点。