AOP(Aspect Oriented Programming)称为面向切面编程,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限,安全控制,等待,性能统计,异常处理等等。Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
Spring支持两种方法,那么我们在使用spring进行动态代理时究竟使用的哪一种方法呢?
spring优先支持实现接口的方式,如果没有接口则使用cglib方式。
我就使用注解来举例,所需要的maven依赖如下
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<?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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--扫描包下所有的注解-->
<context:component-scan base-package="com.luo.spring.log,com.luo.spring.test"/>
<!--让spring来自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
AOP的5个通知注解
@before:前置通知
@after: 后置通知
@AfterReturning: 返回通知
@AfterThrowing:异常通知
@Around:环绕通知
execution(访问权限修饰符 返回值 方法权限定类名(参数))
通配符: * 匹配单个或多个字符,中间是*,单层路径,开头就是*,代表任意层路径
通配符: . .任意方法参数,任意路径
执行的顺序
正常执行:before --> after --> afterReturning
异常执行:before --> after --> afterThrowing
如果有环绕通知,顺序改变
环绕前置 /普通前置 -->目标方法执行 --> 环绕正常返回/出现异常–> 环绕后置-> 普通后置–> 普通返回/出现异常
注册进入IOC容器,并且让spring知道这是一个切面类
@Aspect
@Component
public class LogUtils {
@Before("execution(* com.luo.spring.log.EatRice.*(..))")
public void print(JoinPoint joinPoint){
//JoinPoint 封住了目标方法的详细信息
//获取目标方法的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("Before....");
}
//告诉spring哪个参数是用来接受返回值
@AfterReturning(value = "execution(* com.luo.spring.log.EatRice.*(..))",returning = "result")
public void print01(JoinPoint joinPoint,Object result){
System.out.println("AfterReturning.....方法返回值为:"+result);
}
//告诉spring哪个参数是用来接受异常
@AfterThrowing(value = "execution(* *.log.*.*(..))",throwing = "e")
public void print02(JoinPoint joinPoint,Exception e){
System.out.println("AfterThrowing......异常信息为:"+e );
}
}
@Around(value = "execution(* com.luo.spring.log.EatRice.*(..))")
public Object print03(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = null;
try {
System.out.println("前置通知");
//proceed()可以不用参数,传参数可以修改参数
result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
System.out.println("返回通知.....");
}catch (Exception e){
System.out.println("异常通知");
System.out.println(e.getCause());
}finally {
System.out.println("后置通知");
}
return result;
}
每一个注解都有切入点表达式,如果要修改切入的具体位置,每一个都要修改,很是麻烦,因此,可以把它抽取出来
//抽取可重用的切入点表达式
@Pointcut(value = "execution(* com.luo.spring.log.EatRice.*(..))")
public void print6(){
}
//这里直接填方法名
@Before("print6()")
public void print(){
System.out.println("Before....");
}
下面是基于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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
" >
<bean id="loggerTest" class="dao.LoggerTest"/>
<bean id="loggerDemo" class="dao.LoggerDemo"/>
<aop:config>
<!--切面类-->
<aop:aspect ref="loggerDemo">
<!--切入点-->
<aop:pointcut expression="execution(* *.*(..))" id="aa"/>
<!--通知-->
<aop:after method="after" pointcut-ref="aa"/>
</aop:aspect>
</aop:config>
</beans>
@Test
public void a(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring3.xml");
LoggerTest loggerTest = (LoggerTest) applicationContext.getBean("loggerTest");
loggerTest.test();
}