- 什么是AOP
AOP,顾名思义面向切面编程。要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。可以把AOP当成是OOP(面向对象)的一种有效补充。
- Spring AOP与AspectJ的区别
- SpringAOP是基于动态代理模式来实现的。而动态代理的实现也是依靠俩种方式,当有接口的时候使用的是JDK提供的动态代理实现,没有接口的时候使用的是CGLIB字节码增强技术来实现
- SpringAOP只能作用于被SpringIOC容器管理的bean上,如果对象不被SpringIOC容器管理则SpringAOP也无能为力了。
- SpringAOP需要在容器启动的时候创建目标的代理对象,而且在方法调用上也增加了方法栈的深度,所以性能上比AspectJ差一点。
- AspectJ与SpringAOP不同的是,它是静态织入(通过修改代码来实现),它的织入时机可以是:
Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。- 它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。
- 因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。
- 关键性的相关术语
Advice:增强,主要是用来定义横切面代码(切面逻辑)
Joinpoint:连接点,程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理。在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点。
Pointcut:切点,我觉得它是一段关于连接点的描述信息(描述了哪些连接点会被切面织入)
Aspect:切面,其实就是增强和切点的结合体
Advisor:通知器,与切面类似(也是由增强和切点组合的),只是说使用场景不太一样,切面一般我们用来做日志、缓存处理等,而通知器可以用来做事务处理(Spring事务就是这样的),而且俩者在实现的方式上也有细微的不同。(主要就是advisor的话它的增强必须实现Advice接口,而aspect的话普通的bean用作增强就可以了)
使用Spring AOP
- spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
- Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间 <aop />
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。
spring 1.2 基于接口的配置
先定义俩个接口UserService和OrderService以及相关的实现类:
public interface UserService {
User createUser(String firstName, String lastName, int age);
User queryUser();
}
public class UserServiceImpl implements UserService {
private static User user = null;
public User createUser(String firstName, String lastName, int age) {
user = new User();
user.setFirstName(firstName);
user.setLastName(lastName);
user.setAge(age);
return user;
}
public User queryUser() {
return user;
}
}
public interface OrderService {
Order createOrder(String username, String product);
Order queryOrder(String username);
}
public class OrderServiceImpl implements OrderService {
private static Order order = null;
public Order createOrder(String username, String product) {
order = new Order();
order.setUsername(username);
order.setProduct(product);
return order;
}
public Order queryOrder(String username) {
return order;
}
}
接下来定义俩个advice(增强):
public class LogArgsAdvice implements MethodBeforeAdvice {
// 方法调用前输出 调用方法名字和参数
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("准备执行方法: "+ method.getName()+", 参数列表: " + Arrays.toString(args));
}
}
public class LogResultAdvice implements AfterReturningAdvice {
// 方法调用后输出结果
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("方法返回: " + returnValue);
}
}
OK,现在开始进行xml的配置工作:
<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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 实现的俩个增强纳入到IOC容器管理里-->
<bean id="logArgsAdvice" class="com.suxin.aop.advice.LogArgsAdvice" />
<bean id="logResultAdvice" class="com.suxin.aop.advice.LogResultAdvice" />
<!-- 俩个接口的实现类纳入到IOC容器的管理里-->
<bean id="userServiceImpl" class="com.suxin.aop.service.impl.UserServiceImpl" />
<bean id="orderServiceImpl" class="com.suxin.aop.service.impl.OrderServiceImpl" />
<!-- 配置UserService的代理-->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理接口 -->
<property name="proxyInterfaces">
<list>
<value>com.suxin.aop.service.UserService</value>
</list>
</property>
<!-- 代理的具体实现-->
<property name="target" ref="userServiceImpl" />
<!-- 配置拦截器 这里可以配置advice、advisor、interceptor-->
<property name="interceptorNames">
<list>
<value>logArgsAdvice</value>
<value>logResultAdvice</value>
</list>
</property>
</bean>
<!-- OrderService的代理配置与上面是一致的-->
<bean id="orderServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理接口 -->
<property name="proxyInterfaces">
<list>
<value>com.suxin.aop.service.OrderService</value>
</list>
</property>
<!-- 代理的具体实现-->
<property name="target" ref="orderServiceImpl" />
<!-- 配置拦截器 这里可以配置advice、advisor、interceptor-->
<property name="interceptorNames">
<list>
<value>logArgsAdvice</value>
<value>logResultAdvice</value>
</list>
</property>
</bean>
</beans>
执行一下main方法:
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aops.xml");
UserService userService = (UserService)context.getBean("userServiceProxy");
userService.createUser("Tom","Paul",35);
userService.queryUser();
}
}
看一下控制台打印
准备执行方法: createUser, 参数列表: [Tom, Paul, 35]
方法返回: com.suxin.aop.model.User@229d10bd
准备执行方法: queryUser, 参数列表: []
方法返回: com.suxin.aop.model.User@229d10bd
可以看到针对UserService的接口方法都实现了前后拦截,并且输出了拦截的代码逻辑。
虽然功能实现了,但是还有很多值得完善的地方,我们来一一道明,并且给出解决方案。
缺点:
- 接口的所有方法都被拦截了,我们可能只是想对特定的接口方法进行拦截。
解决方案:在上面配置userServiceProxy的时候,会对里面的interceptorNames属性进行配置,它不仅可以配置advice增强,还可以配置advisor。而advisor可以指定需要对哪些方法进行拦截,然后交由内部的adivce来完成拦截后的切面逻辑工作。
advisor有好几个实现类,我们选择用NameMatchMethodPointcutAdvisor
见字知意,它需要我们给它提供方法名字,这样符合该配置的方法才会做拦截。
直接看xml配置(只看修改的地方了):
<!-- 定义一个advisor-->
<bean id="logCreateAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!-- 内部定义一个advice增强-->
<property name="advice" ref="logArgsAdvice" />
<!-- 定义拦截的方法名称 凡是匹配的方法都会被拦截-->
<property name="mappedNames" value="createUser,createOrder" />
</bean>
<!-- 配置UserService的代理-->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理接口 -->
<property name="proxyInterfaces">
<list>
<value>com.suxin.aop.service.UserService</value>
</list>
</property>
<!-- 代理的具体实现-->
<property name="target" ref="userServiceImpl" />
<!-- 配置拦截器 这里可以配置advice、advisor、interceptor-->
<property name="interceptorNames">
<list>
<value>logCreateAdvisor</value>
</list>
</property>
</bean>
运行main方法,直接看输出:
准备执行方法: createUser, 参数列表: [Tom, Paul, 35]
可以看到只有createUser方法被拦截了,而queryUser方法则是没被拦截。所以第一个缺点的问题我们已经完善了。
- 根据上面的玩法,我们得为每个需要拦截的bean设置一个代理类,这样就非常的不方便了。如果有很多个bean需要,岂不是需要很多的代理类,这样的玩法也不现实。
解决方案:引入AutoProxy自动代理。也就是说当 Spring 发现一个 bean 需要被切面织入的时候,Spring 会自动生成这个 bean 的一个代理来拦截方法的执行,确保定义的切面能被执行。这里强调自动,也就是说 Spring 会自动做这件事,而不用像前面介绍的,我们需要显式地指定代理类的 bean
我们去掉原来的 ProxyFactoryBean 的配置,改为使用 BeanNameAutoProxyCreator 来配置:
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- 配置拦截器-->
<property name="interceptorNames">
<list>
<value>logArgsAdvice</value>
<value>logResultAdvice</value>
</list>
</property>
<!-- 定义匹配的bean的规则-->
<property name="beanNames" value="*ServiceImpl" />
</bean>
修改一下原先的main方法:
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aops.xml");
UserService userService = (UserService)context.getBean("userServiceImpl");
userService.createUser("Tom","Paul",35);
userService.queryUser();
}
}
控制台输出:
准备执行方法: createUser, 参数列表: [Tom, Paul, 35]
方法返回: com.suxin.aop.model.User@161479c6
准备执行方法: queryUser, 参数列表: []
方法返回: com.suxin.aop.model.User@161479c6
俩个问题我们都得到了解决,现在我们来介绍一下DefaultAdvisorAutoProxyCreator
在上面的BeanNameAutoProxyCreator里通过自身匹配方法,然后交由内部配置 advice 来拦截处理;而 DefaultAdvisorAutoProxyCreator 是让 ioc 容器中的所有 advisor 来匹配方法,advisor 内部都是有 advice 的,让它们内部的 advice 来执行拦截处理。
我们需要再回头看下 Advisor 的配置,上面我们用了 NameMatchMethodPointcutAdvisor 这个类:
<bean id="logCreateAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="logArgsAdvice" />
<property name="mappedNames" value="createUser,createOrder" />
</bean>
其实 Advisor 还有一个更加灵活的实现类 RegexpMethodPointcutAdvisor,它能实现正则匹配,如:
<bean id="logArgsAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="logArgsAdvice" />
<property name="pattern" value="com.suxin.aop.service.*.create.*" />
</bean>
之后,我们需要配置 DefaultAdvisorAutoProxyCreator,它的配置非常简单,直接使用下面这段配置就可以了,它就会使得所有的 Advisor 自动生效,无须其他配置。
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
控制台输出:
准备执行方法: createUser, 参数列表: [Tom, Paul, 35]
方法返回: com.suxin.aop.model.User@77167fb7
从结果可以看出,create* 方法使用了 logArgsAdvisor 进行传参输出,query* 方法使用了 logResultAdvisor 进行了返回结果输出。
到这里,Spring 1.2 的配置就要介绍完
Spring 2.0 @AspectJ 配置
注意了,@AspectJ 和 AspectJ 没多大关系,并不是说基于 AspectJ 实现的,而仅仅是使用了 AspectJ 中的概念,包括使用的注解也是直接来自于 AspectJ 的包。
先引入依赖的包吧:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因为我们需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。
开启@AspectJ注解配置的方式有俩种:
1.使用xml配置:
<!-- 开启注解方式来配置AOP-->
<aop:aspectj-autoproxy />
2.使用@EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
开启以后,所有被@Aspect注解的bean都会被Spring当成实现aop的配置类。可以理解为一个Aspect。
直接上代码:
@Aspect
public class LogArgsAspect {
@Pointcut("execution(* com.suxin..service.*.*(..))")
public void businessService2() {
}
@Before("businessService2()")
public void logArgs(JoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前,打印入参: "+ Arrays.toString(joinPoint.getArgs()));
}
}
@Aspect
public class LogResultAspect {
@Pointcut("execution(* com.suxin..service.*.*(..))")
public void businessService2() {
}
@AfterReturning(pointcut = "businessService2()",returning = "result")
public void logResult(Object result) {
System.out.println(result);
}
}
继续看xml的配置:
<!-- 俩个接口的实现类纳入到IOC容器的管理里-->
<bean id="userServiceImpl" class="com.suxin.aop.service.impl.UserServiceImpl" />
<bean id="orderServiceImpl" class="com.suxin.aop.service.impl.OrderServiceImpl" />
<aop:aspectj-autoproxy />
<bean class="com.suxin.aop.aspect.LogArgsAspect" />
<bean class="com.suxin.aop.aspect.LogResultAspect" />
运行main方法,控制台打印:
方法执行前,打印入参: [Tom, Paul, 35]
com.suxin.aop.model.User@2f465398
方法执行前,打印入参: []
com.suxin.aop.model.User@2f465398
ok,这就是基于注解的形式来进行Spring的AOP,当然只是点到为止,还有很多玩法,大家感兴趣自行去发现。
Spring 2.0 schema-based 配置
Spring 2.0 以后提供的基于 <aop /> 命名空间的 XML 配置。这里说的 schema-based 就是指基于 aop 这个 schema。
解析 <aop /> 的源码在 org.springframework.aop.config.AopNamespaceHandler 中。
所有的配置都在 <aop:config> 下面。
<aop:aspect > 中需要指定一个 bean,和前面介绍的 LogArgsAspect 和 LogResultAspect 一样,我们知道该 bean 中我们需要写处理代码。
然后,我们写好 Aspect 代码后,将其“织入”到合适的 Pointcut 中,这就是面向切面。
直接上代码吧:
<!-- 俩个接口的实现类纳入到IOC容器的管理里-->
<bean id="userServiceImpl" class="com.suxin.aop.service.impl.UserServiceImpl" />
<bean id="orderServiceImpl" class="com.suxin.aop.service.impl.OrderServiceImpl" />
<bean id="logArgsAspect" class="com.suxin.aop.aspect.LogArgsAspect" />
<bean id="logResultAspect" class="com.suxin.aop.aspect.LogResultAspect" />
<aop:config>
<!-- 全局的pointcut,可以作用在多个切面里-->
<aop:pointcut id="logResultPointcut" expression="com.suxin.aop.aspect.LogResultAspect.businessService2()"/>
<aop:aspect ref="logArgsAspect">
<!-- 局部的pointcut只能在这个切面里进行使用-->
<aop:pointcut id="internalPointcut" expression="com.suxin.aop.aspect.LogArgsAspect.businessService2()"/>
<!-- 定义拦截逻辑以及引用的切点-->
<aop:before method="logArgs" pointcut-ref="internalPointcut"></aop:before>
</aop:aspect>
<aop:aspect ref="logResultAspect">
<aop:after-returning method="logResult" returning="result" pointcut-ref="logResultPointcut"></aop:after-returning>
</aop:aspect>
</aop:config>
运行main方法,控制台打印:
方法执行前,打印入参: [Tom, Paul, 35]
com.suxin.aop.model.User@31f9b85e
方法执行前,打印入参: []
com.suxin.aop.model.User@31f9b85e
其实基于 XML 的配置也是非常灵活的,这里没办法给大家演示各种搭配,大家抓住基本的 Pointcut、Advice 和 Aspect 这几个概念,就很容易配置了。
网友评论