目录
6.异常通知
异常通知只有在切入点出现异常时才会被触发。如果方法没有异常,异常通知是不会执行的。
6.1 新建通知类
新建com.tong.advice.MyThrow。
MethodInterceptor接口没有方法,但是我们必须严格提供一个下面的方法:
- public void afterThrowing:必须相同
- 必须有Exception参数
也就是说,虽然ThrowsAdvice虽然没有方法,但是我们必须自己写一个和下面一样的方法。
package com.tong.advice;
import org.springframework.aop.ThrowsAdvice;
public class MyThrow implements ThrowsAdvice {
public void afterThrowing(Exception e){
System.out.println("异常通知:"+e.getMessage());
}
}
6.2 配置切面
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tong.mapper,com.tong.service.impl">
</context:component-scan>
<bean id="mybefore" class="com.tong.advice.MyBefore"></bean>
<bean id="myafter" class="com.tong.advice.MyAfter"></bean>
<bean id="myaround" class="com.tong.advice.MyAround"></bean>
<!-- 异常通知 -->
<bean id="mythrow" class="com.tong.advice.MyThrow"></bean>
<aop:config>
<aop:pointcut id="mypoint" expression="execution(*
com.tong.service.impl.PeopleServiceImpl.test())"/>
<!-- 织入异常通知 -->
<aop:advisor advice-ref="mythrow" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
</beans>
6.3 在切入点中写个异常
在PeopleServiceImpl里面随意写个会出现异常的代码
@Service("peopleService")
public class PeopleServiceImpl implements PeopleService {
@Autowired
private PeopleMapperImpl peopleMapperImpl123;
@Override
public void test() {
System.out.println("join pointer 切入点。");
int i = 5/0;// 算数异常
}
}
7.Schema-based方式包含多个相同通知的执行顺序
如果切面中包含多个通知,执行顺序是按照配置顺序执行。
- 前置通知:先配置的先执行
- 后置通知:先配置的后执行
- 环绕通知:先配置的先执行后结束
- 异常通知:先配置的后执行(ThrowsAdvice是AfterAdvice的子接口,AfterReturningAdvice是 AfterAdvice的子接口)
AspectJ实现AOP
1.AspectJ介绍
前面学习的Schema-based方式属于Spring 框架提供的一种AOP实现方式。
AspectJ并不是Spring框架提供的技术,而是一个Java的AOP框架,借助专门的编译器来生成Java的字节码文件,编译完成,就形成了对应的切面。也就是说:AspectJ属于编译期增强
Spring Framework 2.0开始对AspectJ方式进行了支持。
可以使用注解方式,也可以使用XML配置文件方式。
在不考虑通知的参数时,AspectJ方式非常简单,只需要使用POJO+特定配置 。但是如果需要在通知中考虑参数问题和返回值时,相对配置会复杂一些。
2. AspectJ方式通知类型
- 前置通知before
- 后置通知:
- after是否出现异常都执行的后置通知。
- after-returning切入点不出现异常时才执行的后置通知
3、环绕通知around
4、异常通知after-throwing
3.代码实现
3.1创建通知类
Aspectj方式实现AOP的通知类不需要实现任何的接口,直接声明一个普通java类即可,然后在类中直接定义通知方法即可,方法名随意,但是建议方法名见名知意。
package com.tong.advice;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
//前置通知方法
public void before(){
System.out.println("我是前置通知方法...");
}
//后置通知方法
public void after(){
System.out.println("我是后置通知方法...");
}
//环绕通知方法
public Object round(ProceedingJoinPoint pp) throws Throwable {
System.out.println("环绕---前");
//放行
Object proceed = pp.proceed();
System.out.println("环绕---后");
return proceed;
}
//异常通知方法
public void myThrow(Exception ex){
System.out.println("我是异常通知......"+ex.getMessage());
}
}
3.2配置AOP
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置真实对象的bean-->
<bean id="us" class="com.tong.service.impl.UserServiceImpl"></bean>
<!--配置通知bean-->
<bean id="advice" class="com.tong.advice.MyAdvice"></bean>
<!--配置AOP组装-->
<aop:config>
<!--基于Aspectj方式配置-->
<aop:aspect ref="advice">
<!--声明切点-->
<aop:pointcut id="mp" expression="execution(* com.tong.*.impl.*.*(..))"/>
<!--配置通知方法-->
<aop:before method="before" pointcut-ref="mp"></aop:before>
<!--切点正常执行才会被执行-->
<aop:after-returning method="after" pointcut-ref="mp"></aop:afterreturning>
<!--切点是否正常执行都会执行-->
<!--<aop:after method="after" pointcut-ref="mp"></aop:after>-->
<aop:around method="round" pointcut-ref="mp"></aop:around>
<!-- 必须使用throwing声明异常参数名 -->
<aop:after-throwing method="myThrow" pointcut-ref="mp" throwing="ex">
</aop:after-throwing>
</aop:aspect>
</aop:config>
</beans>
注意:
after和after-returning,after无论切点是否出现异常都执行的后置通知,after-returning只有在切点正 常执行完成,才会触发的通知。
3. 在通知中获取切入点参数的写法
package com.tong.advice;
import org.aspectj.lang.ProceedingJoinPoint;
/*
如果希望获取切入点方法的参数,推荐把通知的参和切入点的参数写成一致。
*/
public class MyAdvice2 {
public void mybefore(int id1, boolean bool1){
System.out.println("前置通知"+id1+","+bool1);
}
public void myafter(int id1, boolean bool1){
System.out.println("后置:"+id1+","+bool1);
}
public void myafter2(int id1, boolean bool1){
System.out.println("后置2:"+id1+","+bool1);
}
public Object myaround(ProceedingJoinPoint pjp,int id1, boolean bool1)
throws Throwable {
System.out.println("环绕前置");
Object result = pjp.proceed();
System.out.println("环绕后置");
return result;
}
public void mythrow(Exception e,int id1, boolean bool1){
System.out.println("异常通知:"+e.getMessage()+",id:"+id1+",bool1:"+bool1);
}
}
在配置文件中配置
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tong.mapper,com.tong.service.impl">
</context:component-scan>
<bean id="myadvice2" class="com.tong.advice.MyAdvice2"></bean>
<aop:config>
<!-- ref引用自定义通知类对应bean的id -->
<aop:aspect ref="myadvice2">
<!-- args写在execution括号外面 -->
<!-- args括号内的名称和通知中方法参数名相同-->
<aop:pointcut id="mypoint" expression="execution(* com.tong.service.impl.PeopleServiceImpl.test(int,boolean)) and args(id1,bool1)"/>
<aop:before method="mybefore" pointcut-ref="mypoint" arg-names="id1,bool1"></aop:before>
<aop:after-returning method="myafter" pointcut-ref="mypoint" arg-names="id1,bool1"></aop:after-returning>
<aop:after method="myafter2" pointcut-ref="mypoint" arg-names="id1,bool1">
</aop:after>
<!-- names里面取值要和通知中方法参数名完全一致。pjp由Spring帮助自动注入进来 --
>
<aop:around method="myaround" pointcut-ref="mypoint" argnames="pjp,id1,bool1" ></aop:around>
<!-- 不要忘记通过throwing属性声明异常参数名称-->
<aop:after-throwing method="mythrow" pointcut-ref="mypoint" argnames="e,id1,bool1" throwing="e"></aop:after-throwing>
</aop:aspect>
</aop:config>
</beans>
4.Schema-based和Aspectj的区别
Schema-based:基于模式的。基于接口实现的。每个通知都需要实现特定的接口类型,才能确定通知的类型。由于类已经实现了接口,所以配置起来相对比较简单。尤其是不需要在配置中指定参数和返回值类型。
AspectJ方式:是基于配置实现的。通过不同的配置标签告诉Spring通知的类型。AspectJ方式对于通知类写起来比较简单。但是在配置文件中参数和返回值需要特殊进行配置。
因为Schame-based是运行时增强,AspectJ是编译时增强。所以当切面比较少时,性能没有太多区别。但是当切面比较多时,最好选择AspectJ方式,因为AspectJ方式要快很多。
Schema-based是Spring框架自己的AOP实现。而AspectJ属于Spring框架对AspectJ的集成支持。
纯注解方式实现AOP
1.总体配置说明
在Spring 框架中,AOP注解只支持AspectJ方式的注解。通过注解来简化AspectJ方式的XML配置。 我们可以通过XML+注解方式实现AOP,也可以使用纯注解方式实现AOP。
1.1 XML+注解
如果使用XML+注解方式实现AOP,需要编写applicationContext.xml配置文件,开启注解扫描和启用 AOP注解
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置注解扫描路径:-->
<context:component-scan base-package="com.tong.annotation">
</context:component-scan>
<!--配置AOP注解生效-->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
</beans>
然后在测试类中,使用locations属性加载配置文件
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class AnnotationTest {}
1.2 纯注解方式
如果使用纯注解方式,其实就是使用Java Config方式,把applicationContext.xml替换为配置类
@Configuration
// 等效于<context:component-scan base-package="com.tong.annotation">
@ComponentScan("com.tong.annotation")
// 等效于<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy
public class AnnotationConfig {}
然后在测试类中,使用classes属性加载配置类
@SpringJUnitConfig(classes = AnnotationConfig.class)
public class AnnotationTest {}