1、AspectJ 通知类型
AOP 联盟定义通知类型,AOP联盟的jar都是接口,必须要有实现类。 AspectJ通知类型只定义类型名称,以及方法格式,总共有6种; 1.brfore:前置通知(应用:各种校验) 在方法执行前执行,如果通知抛出异常,将不会执行方法 2.afterReturning:后置通知(应用:常规数据处理) 方法正常返回后执行,如果方法中抛出异常,通知将无法执行 3. around:环绕通知(应用:十分强大,可以做任何事) 方法执行前后分别执行,可阻止方法执行 4.afterThrowing:抛出异常通知(应用:包装异常信息) 方法抛出异常后执行,如果方法没有抛出异常,无法执行 5.after:最终通知(应用:清理现场) 方法执行完毕后执行,无论方法中是否出现异常都会执行(类似于finally代码块)
2、回顾基于XML配置AOP
在上一篇笔记*Spring从入门到精通—Spring AOP的XML配置和使用*中讲了如何使用xml的方式配置和使用Spring的AOP,这里我们再回顾一下:
首先编写一个service接口,模拟要处理的业务
package com.xzy.service;
public interface UserSeviceBase {
/**
* 增加用户
*/
public void addUser();
/**
* 删除用户
* @param id
*/
public int deleteUser(int id);
/**
* 更新用户
* @param id
*/
public void updateUser(int id);
}
实现sevice接口:
package com.xzy.service;
public class UserSeviceImpl implements UserSeviceBase {
@Override
public void addUser() {
System.out.println("增加了1个用户");
}
@Override
public int deleteUser(int id) {
System.out.println("删除了id为"+id+"的用户");
return id;
}
@Override
public void updateUser(int id) {
System.out.println("id为"+id+"的用户更新了");
}
}
编写切面类:写一个方法before(),他是在目标方法执行需要增强的功能
package com.xzy.Aspect;
import org.aspectj.lang.JoinPoint;
public class MyAspect2 {
/*
*前置通知
*JoinPoint:连接点
*/
public void before(JoinPoint jp){
System.out.println("前置通知........"+jp.getSignature().getName()); //得到方法的名字
}
}
在XML中的配置如下:
ApplicationContext2.xml
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置bean-->
<bean id="userService" class="com.xzy.service.UserSeviceImpl"></bean>
<!--配置切面类-->
<bean id="aspect" class="com.xzy.Aspect.MyAspect2"></bean>
<!--配置aop-->
<aop:config>
<!--指定切面-->
<aop:aspect ref="aspect">
<!--指定切入点-->
<aop:pointcut id="poincut1" expression="execution(* com.xzy.service.*.*(..))"/>
<!--前置通知-->
<aop:before method="before" pointcut-ref="poincut1"/>
</aop:aspect>
</aop:config>
</beans>
测试:
package com.xzy;
import com.xzy.service.UserSeviceBase;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
@Test
public void test(){
ApplicationContext context=new ClassPathXmlApplicationContext("ApplicationContext2.xml");
//从Spring容器中拿代理对象
UserSeviceBase userSevice= (UserSeviceBase) context.getBean("userService");
userSevice.deleteUser(23);
userSevice.updateUser(3);
}
}
测试结果:如下图所示,前置通知确实起作用了,在目标方法执行之前就执行了

下面我们在来测测<aop:advisor>
中的其他通知方式
<aop:after-returning>
在切面类中增加方法:afterReturning(JoinPoint jp,Object obj)
,其中第一个参数是连接点,第二个参数是目标方法运行后的返回值。要获得返回返回值需要在配置中设置returning="obj",就是把这个第二个参数的名字放进去,Spring就会把返回值注入。
package com.xzy.Aspect;
import org.aspectj.lang.JoinPoint;
public class MyAspect2 {
/*
*方法执行前的通知
*/
public void before(JoinPoint jp){
System.out.println("前置通知........"+jp.getSignature().getName()); //得到方法的名字
}
/*
*方法返回后的通知
*/
public void afterReturning(JoinPoint jp,Object obj){
System.out.println("后置通知........"+jp.getSignature().getName()); //得到方法的名字
System.out.println("方法的返回值是:"+obj);
System.out.println("----------------------------------");
}
}
配置新增的切面类方法:
<!--配置bean-->
<bean id="userService" class="com.xzy.service.UserSeviceImpl"></bean>
<!--配置切面类-->
<bean id="aspect" class="com.xzy.Aspect.MyAspect2"></bean>
<!--配置aop-->
<aop:config>
<!--指定切面-->
<aop:aspect ref="aspect">
<!--指定切入点-->
<aop:pointcut id="poincut1" expression="execution(* com.xzy.service.UserSeviceImpl.*(..))"/>
<!--前置通知-->
<aop:before method="before" pointcut-ref="poincut1"/>
<!--后置通知-->
<aop:after-returning method="afterReturning" pointcut-ref="poincut1" returning="obj"></aop:after-returning>
</aop:aspect>
</aop:config>
测试代码不变,测试结果如下:

#####<aop: around> around具有before和after-returning两者的功能,这里就不在重复测试了。所以一般使用了around就不在使用brfore和after-returning
<aop: after-throwing>、<aop: after>
切面类中增加方法afterThrowing
和after
/**
* 抛出异常后通知
* @param jp 连接点
* @param e 异常
*/
public void afterThrowing(JoinPoint jp,Throwable e){
System.out.println("抛出异常通知....."+jp.getSignature().getName()+e.getMessage());
}
public void after(JoinPoint jp){
System.out.println("最终通知......"+jp.getSignature().getName());
}
配置xml
<!--配置aop-->
<aop:config>
<!--指定切面-->
<aop:aspect ref="aspect">
<!--指定切入点-->
<aop:pointcut id="poincut1" expression="execution(* com.xzy.service.UserSeviceImpl.*(..))"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="poincut1"></aop:around>
<!--异常通知:当目标方法发生异常后会执行 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="poincut1" throwing="e"/>
<!--最终通知:无论方法有没有发生异常,都会执行-->
<aop:after method="after" pointcut-ref="poincut1"/>
</aop:config>
并在deleteUser方法中主动抛出异常:
@Override
public int deleteUser(int id) {
System.out.println("删除了id为" + id + "的用户");
throw new RuntimeException(new Exception("自定义异常...."));
}
测试结果如下:

如果去掉异常的测试结果如下:

2、基于Annotation配置AOP
既然使用注解配置,那就全部用注解,包括配置文件都用注解+Java类来实现 * 编写配置类Appconfig.java替代xml文件
package com.xzy;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration //告诉spring这是配置文件
@EnableAspectJAutoProxy //开启aop自动代理
@ComponentScan(basePackages = {"com.xzy"}) //告诉spring去哪里扫描注解
public class AppConfig {
//这里头以后可以写各种配置,这个类的作用就和XML文件的作用一样
}
当然这段java代码可以用下面这段XML配置文件替代
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解功能-->
<context:annotation-config/>
<!--告诉Spring去哪里扫描注解-->
<context:component-scan base-package="com.xzy"></context:component-scan>
<!--配置aop自动代理-->
<aop:aspectj-autoproxy/>
</beans>
编写一个日志记录的切面类LoggerApsect.java 在切面类中可以使用如下几个注解来定制一个切面:
- @Aspect:告诉Spring这是切面类
- @Brfore:前置通知
- @AfterRuning:返回后通知
- @Around:环绕通知,是@Brfore和@AfterRuning<的结合,功能十分强大
- @After-Throwing:抛出异常后的通知,没有异常不会执行
- @After:最终通知,无论有没有异常一定会执行的
- @PointCut:定义切点
package com.xzy.Aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 一个记录各种操作的日志切面类
*/
@Component //告诉Spring你要把这个类给我实例化了
@Aspect //告诉Spring这是一个切面类
public class LoggerAspect {
//声明一个公共的切点:将com.xzy.service包下的所有以Impl结尾的方法作为切入点,切入点中可以没有任何代码实现,只是让他在形式上存在即可
@Pointcut("execution(* com.xzy.service.*Impl.*(..))")
public void pointcut() {
}
@Before("execution(* com.xzy.service.*Impl.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("before给" + joinPoint.getSignature().getName() + "方法作前日志.........." + new Date());
}
@AfterReturning(pointcut = "pointcut()", returning = "retValue")
public void afterReturning(JoinPoint joinPoint, Object retValue) {
System.out.println("afterReturning给" + joinPoint.getSignature().getName() + "方法作后日志.........." + new Date());
System.out.println("方法的返回值是:" + retValue);
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around给" + joinPoint.getSignature().getName() + "方法作前日志.........." + new Date());
Object retValue = joinPoint.proceed();
System.out.println("around给" + joinPoint.getSignature().getName() + "方法作前后志.........." + new Date());
return retValue;
}
@AfterThrowing(pointcut = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("给" + joinPoint.getSignature().getName() + "方法作异常日志:" + new Date() + "抛出" + e.getMessage());
}
@After("pointcut()")
public void afterAll(JoinPoint joinPoint) {
System.out.println("给" + joinPoint.getSignature().getName() + "方法作最终日志.........." + new Date());
}
}
测试类如下:
package com.xzy;
import com.xzy.service.UserSeviceBase;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {AppConfig.class})
public class AppTest {
private static Logger log= Logger.getLogger(AppTest.class);
@Autowired
UserSeviceBase userSevice;
@Test
public void test3(){
userSevice.deleteUser(12);
userSevice.updateUser(34);
}
}
测试结果:

现在去掉before、after-return以及在deleteUser中抛出一个异常:
