前言
AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能。
AspectJ应用到java代码的过程称为织入,对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程。
对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术。
ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
在Spring中使用AspectJ需要添加AspectJ的依赖包:
<!-- 基于AspectJ的aop依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
和动态代理的实现过程类似:
基于Xml文件实现
1、目标接口
public interface UserService {
void query(String sql);
}
2、目标实现类
public class UserServiceImpl implements UserService{
@Override
public void query(String sql) {
System.out.println(".......query........" + sql);
}
}
3、这里和动态代理的实现差异性较大,直接写增强类,也就是普通的Java类
public class MyAdvice {
public void before(){
System.out.println("xml方式 装配 before .....");
}
}
4、接着就是在Spring配置文件中配置AOP切面,将第三步创建的增强类交由Spring容器管理:
<?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">
<!-- 配置目标对象 -->
<bean id="uservice" class="com.lks.aspectJ.UserServiceImpl"></bean>
<!--xml文件-->
<!-- 配置通知类 -->
<bean id="myAdvice" class="com.lks.aspectJ.MyAdvice"></bean>
<!-- AOP配置 -->
<aop:config>
<!-- 配置AOP切面,切面是由通知和切入点组成的 -->
<aop:aspect ref="myAdvice">
<!-- before:前置通知 -->
<!-- pointcut:编写切入点表达式 ,去定位需要切入的方法是哪个 -->
<!-- method:增强类中的方法 -->
<aop:pointcut id="queryPointcut" expression="execution(void com..UserServiceImpl.query(..))"></aop:pointcut>
<aop:before method="before"
pointcut-ref="queryPointcut"
/>
</aop:aspect>
</aop:config>
</beans>
其中< aop:aspect >标签中的ref要与配置通知类的id一致,pointcut-ref指向 < aop:pointcut >中的id。需要注意的是切入点表达式的格式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
execution:必须要
修饰符:可省略
返回值类型:必须要,但是可以使用通配符
包名:多级包之间使用.分割,包名可以使用代替,多级包名可以使用多个代替, 如果想省略中间的包名可以使用 . .
类名:可以使用代替,也可以写成ServiceImpl
方法名:也可以使用好代替,也可以写成add*
参数:参数使用*代替,如果有多个参数,可以使用 . .代替
5、测试方法就和普通的bean调用一样,但实现了增强功能
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-aspectJ.xml")
public class TestAspectJ {
@Autowired
private UserService userService;
@Test
public void testAspectJByXml() {
userService.query("select * from tableName");
}
}
基于注解实现
前面的步骤不需要改变,主要变化的是第三步,在增强类中添加注解:
@Component("myAspect")
@Aspect
public class MyAspect {
@Before(value = "execution(void com..UserServiceImpl.query(*))")
public void before(){
System.out.println("注解方式 before..........");
}
}
4、在Spring文件中配置aop:
<!--注解方式-->
<!--配置切面类-->
<context:component-scan base-package="com.lks.aspectJ"></context:component-scan>
<!--开启AOP自动代理-->
<aop:aspectj-autoproxy/>
另外出了前置通知以外,还有后置通知、最终通知、环绕通知、异常抛出通知,共五种通知类型:
前置通知:
***执行时机:目标对象方法之前执行通知
***配置文件:<aop:before method=“before” pointcut-ref=“myPointcut”/>
***应用场景:方法开始时可以进行校验
后置通知:
***执行时机:目标对象方法之后执行通知,有异常则不执行了
***配置文件:<aop:after-returning method=“afterReturning” pointcut-ref=“myPointcut”/>
***应用场景:可以修改方法的返回值
最终通知:
***执行时机:目标对象方法之后执行通知,有没有异常都会执行
***配置文件:<aop:after method=“after” pointcut-ref=“myPointcut”/>
***应用场景:例如像释放资源
环绕通知:
***执行时机:目标对象方法之前和之后都会执行。
***配置文件:<aop:around method=“around” pointcut-ref=“myPointcut”/>
***应用场景:事务、统计代码执行时机
异常抛出通知:
***执行时机:在抛出异常后通知
***配置文件:<aop:after-throwing method=" afterThrowing " pointcut- ref=“myPointcut”/>
***应用场景:包装异常