Aop的细节
1.Aop底层是动态代理
获取类型是接口
Aop的底层就是动态代理,容器中保存的组件是他的代理对象,
Calculator bean = app.getBean(Calculator.class);
System.out.println(bean);//com.jiang.impl.MyCalculator@1bb5a082
System.out.println(bean.getClass());//class com.sun.proxy.$Proxy15
打印 bean 是com.jiang.impl.MyCalculator@1bb5a082,看起来bean是MyCalculator类型的,
但实际上当你打印bean.getClass得到class com.sun.proxy.$Proxy15,发现实际类型是代理对象
所以如果从容器拿对象,通过类型拿需要写接口类型而不是本类,或者你通过id获取。
获取类型是本类
即使没有接口,也可以创建对象,获取的时候就是本类
我们此时将实现类中的接口给注释掉
public class MyCalculator /*implements Calculator*/ {
然后运行
MyCalculator bean = app.getBean(MyCalculator.class); 使用的是本类获取
System.out.println(bean);//com.jiang.impl.MyCalculator@740773a3
System.out.println(bean.getClass());//class com.jiang.impl.MyCalculator$$EnhancerBySpringCGLIB$$44159919
还是一个代理对象,只是CGLIB帮我们创建好的对象
2.切入点表达式
固定格式:execution(访问权限符 返回值类型 方法全签名(参数表))
通配符
*
:
匹配一个或者多个字符execution(public int com.jiang.impl.*(int,int))
匹配任意一个参数 假设我的add(int,int) 有重载方法add(int,double)
那么我此时的就无法进行切入,因为我们的表达式的参数值是int,int
所以此时就需要execution(public int com.jiang.impl.*(int,*))
权限位置不能表示任意,不写表示任意:execution(* com..impl.*(..))
..
:
匹配所有参数的方法 execution(public int com.jiang.impl.*(..))
匹配任意多层路径 execution(public int com..impl.*(..))
com包下任何多层路径的impl包
最模糊:execution(* *.*(..))
任意权限下 任意返回值 任意包任意类任意方法下任意参数
最精确的:execution(public int com.jiang.impl.MyCalculator.add(int,int))
&&与:切入的位置要满足这两个表达式
execution(public int com..impl.*(..))&& execution(* com.jiang.impl.*(int,int))
add(int,double)满足第一个,不满足第二个,所以不行
||或:切入的位置满足一个即可
!:只要不是这种的都切
抽取可重用的切入点表达式:1.声明一个没实现的带void的方法 2.在方法上@Point注解写需要抽取的表达式
@Point("exect.......")
public void demo(){}
//使用
@Before(demo())
3.通知方法执行顺序
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
通知方法的执行顺序:
正常执行:@Before(前置通知)=》@After(后置通知)=》@AfterReturning(正常返回)
异常执行:@Before(前置通知)=》@After(后置通知)=》@AfterThrowing(异常返回)
4.JoinPoint获取详细信息
我们可以在通知方法运行的时候,拿到目标方法的详细信息
参数JoinPoint
只需要为通知方法的参数列表上写一个参数JoinPoint(封装了当前目标方法的详细信息)
@Aspect
@Component
public class LogUtil {
//想在执行目标之前运行,写入切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("execution(public int com.jiang.impl.MyCalculator.add(int,int))")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行时候的参数
Object[] args = joinPoint.getArgs();
//获取到目标方法的签名
Signature signature = joinPoint.getSignature();
//从签名中获取方法名
String name = signature.getName();
System.out.println("【"+name+"】方法开始前,参数值是【"+Arrays.asList(args)+"】");
}
}
通过name也可以简写为 joinPoint.getSignature().getName()
参数值简写为 Arrays.asList(joinPoint.getArgs())
@AfterReturning
@AfterReturning(value = "execution(public int com.jiang.impl.MyCalculator.*(..))",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法正常执行完成,计算结果是"+result);
}
告诉Spring这个result就是用来接受返回值的:returning = "result"
@AfterThrowing
@AfterThrowing(value = "execution(public int com.jiang.impl.MyCalculator.*(..))",throwing = "e")
public static void logExc(JoinPoint joinPoint,Exception e){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法出现异常,异常是"+e);
}
告诉Spring这个e就是用来接受异常的throwing = "e"
唯一的要求就是参数列表一定不能乱写
- 通知方法是Spring利用反射调的,每次方法调用得确定这个方法的参数表的值;
- 参数表上的每一个参数,Spring必须都得知道是谁
5.环绕通知
@Around:环绕,Spring中最强大的通知,四合一通知就是环绕通知
@Around("execution(public int com.jiang.impl.MyCalculator.*(..))")
public static Object myAround(ProceedingJoinPoint pjp) throws Throwable {
/*
环绕通知下有一个参数:ProceedingJoinPoint pjp,这个方法很强大。
*/
//获取方法参数
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
Object proceed = null;
try{
//就是利用反射调用目标方法即可 它就是method.invoke
System.out.println("在proceed前就是前置通知 @Before");
System.out.println("【环绕前置】【"+name+"方法开始】");
pjp.proceed(args);
System.out.println("在proceed后就是 @AfterReturning");
System.out.println("【环绕返回】【"+name+"方法返回,返回值】"+proceed);
}catch (Exception e){
System.out.println("这就是异常通知 @AfterThrowing");
System.out.println("【环绕异常】【"+name+"方法返回,异常】"+e);
throw new Exception(e);//为了让外面接收到异常,一定要抛出去
}finally {
System.out.println("结束通知 @After");
System.out.println("【环绕后置】【"+name+"方法结束】");
}
System.out.println("环绕。。。");
//反射调用后一定要返回出去
return proceed;
}
- 环绕通知下有一个参数:ProceedingJoinPoint pjp,这个方法很强大。
- pjp.proceed就是利用反射调用目标方法即可 它就是method.invoke
- 在proceed前就是前置通知 @Before
- 在proceed后就是 @AfterReturning
- 在catch中 这就是异常通知 @AfterThrowing
- 在finally 就是后置通知 @After
- 反射调用后一定要返回出去
- 为了让外面接收到异常,一定要抛出
顺序:环绕前置--》普通前置--》目标方法执行--》环绕正常返回/出现异常--》环绕后置--》普通后置--》普通返回/异常
6.多切面运行顺序
@Order(1) 使用order改变切面顺序,数值越小,优先级越高
Aop使用场景
-
AOP做日志保存到数据库
-
AOP做权限验证
-
AOP做安全检查
-
AOP做事务控制
AOP基于XML
基于注解的Aop过程
- 将切面类和目标类都加入到ioc容器中
- 告诉Spring那个是切面类 @Aspect
- 在切面类中通过五个通知注解配置切面中这些通知方法何时运行
- 开启基于注解的AOP功能
基于XML的Aop过程
1.注册bean
<bean id="myCalculator" class="com.jiang.impl.MyCalculator"></bean>
<bean id="logUtil" class="com.jiang.utils.LogUtil"></bean>
2.需要aop空间
<aop:config>
<!--制定切面 ref指定切面 相当于加了@Aspect -->
<aop:aspect ref="logUtil">
<!--那个方法是前置通知 method=@Before point=切入表达式-->
<aop:before method="logStart" pointcut="execution(* com.jiang.impl.MyCalculator.*(..))" />
<!--抽取切面表达式 -->
<aop:pointcut id="mypoint" expression="execution(* com.jiang.impl.MyCalculator.*(..))"/>
<!--通过pointcut-ref引入-->
<aop:after-returning method="logReturn" pointcut-ref="mypoint" returning="result"/>
<aop:after-throwing method="logExc" pointcut-ref="mypoint" throwing="e"/>
</aop:aspect>
</aop:config>
注解:快速方便 配置:功能完善;
重要的用配置,不重要的用注解
声明式事务
Spring提供了JdbcTemplate能快速的帮我们操作数据库
声明式事务:以前通过复杂的编程来编写一个事物,替换需要告诉Spring那个方法是事务方法即可;Spring自动进行事务控制;
事务要么都执行,要么都不执行
事务四个关键属性ACID
- 原子性(atomicity)
- 一致性(consistency)
- 隔离性(isolation)
- 持久性(durability)
使用
1.配置事务管理器,让其进行事务控制,一定要导入面向切面的包
<bean id="dateSource" class="xx..xxxxxx.DataSoruce">
数据源.......
</bean>
2.开启基于注解的事务控制模式,依赖tx空间
<tx:annotation-driven transaction-manager = "dateSource"/>
3.给事务方法加注解 @Transactional