前言:
上一篇学习了Spring AOP(面向切面编程)的基础,在Spring Bean 的配置文件中代理工厂ProxyFactoryBean 的写法似乎并没有见到切入点(pointcut),切面(aspect),织入(weaving)等等的英文所在,那是从哪里来的这些概念呢?这篇文章就告诉你aop的真正写法。
1 配置ProxyFactoryBean
上篇博客地址:Spring学习(7)-AOP面向切面编程
前言:
上一篇对于spring aop仅仅使用代理工厂ProxyFactoryBean ,这是最基础的spring aop写法,而且每一个通知/增强类都不是POJO(简单java对象),它都是实现了Spring提供的接口,复习一下:
- 前置通知:MethodBeforeAdvice
- 后置通知:AfterReturningAdvice
- 环绕通知:MethodInterceptor(只有这个是aopalliance包内的,其他均为spring aop包)
- 异常通知:ThrowsAdvice
- 引介通知:IntroductionInterceptor
然后我们在xml文件中配置一个ProxyFactoryBean
的 Bean,将实现接口的通知类放到代理工厂的interceptorNames
属性中,再指定代理工厂代理的主业务接口和实现主业务接口的目标类,将这些通知织入到目标类中,实现aop的思想。
我就再使用一下上次的例子:
BuyPhoneAroundAdvice (买手机的环绕通知类)
package com.cheng.spring.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
//环绕通知需要实现MethodInterceptor接口
//环绕通知:在主业务的前,后执行服务功能
public class BuyPhoneAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//环绕通知的前置
System.out.println("同学,您定制的钛金手机给您送来了,赶紧拍照发朋友圈---环绕前置");
//执行核心业务,作为环绕的前后通知分割点
invocation.proceed();
System.out.println("用了两天就坏了,智商税还交的不够啊!---环绕后置");
//环绕通知的后置
return null;
}
}
BuyPhone(买手机的主业务接口)
package com.cheng.spring.service;
//主业务,购买手机服务
public interface BuyPhoneService {
//编写业务方法
public void giveMeAphone(String name, String pass);
}
BuyPhoneImpl (买手机的主业务实现类)
ackage com.cheng.spring.serviceImpl;
import com.cheng.spring.service.BuyPhoneService;
public class BuyPhoneServiceImpl implements BuyPhoneService{
@Override
public void giveMeAphone(String name, String pass) {
System.out.println("我的身份只有8848钛金手机才配得上,8848你值得拥有");
}
}
我想通过aop来将环绕通知类切入到我的买手机主业务实现类,需要在applicationContext.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:p="http://www.springframework.org/schema/p"
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">
<!-- 配置目标对象-->
<bean id="buyPhoneService" class="com.cheng.spring.serviceImpl.BuyPhoneServiceImpl"/>
<!-- 配置切面:环绕通知 -->
<bean id="buyPhoneBeforeAdvice" class="com.cheng.spring.advice.BuyPhoneBeforeAdvice"/>
<!-- 使用代理工厂将切面织入目标对象 -->
<bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.cheng.spring.service.BuyPhoneService"
p:target-ref="buyPhoneService"
p:interceptorNames="buyPhoneAroundAdvice"
p:proxyTargetClass="false"/>
</beans>
这样我们通过容器拿到代理工厂返回的接口,来调用它的方法
package com.cheng.spring.test;
import com.cheng.spring.service.BuyPhone;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//如何通过context模块加载配置文件,获取bean实例
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
//如果要启动主业务,我需要获取哪个bean的实例?
BuyPhone buyPhone = (BuyPhone)context.getBean("factoryBean");
buyPhone.giveMeAphone("此成", "123");
}
}
结果:
后语:
只配置代理工厂会对目标类的所有方法拦截
上方实现一个简单环绕通知,且是对买手机这个实现类的所有方法起作用的,什么意思呢?
我给接口和实现类加一个方法:
package com.cheng.spring.serviceImpl;
import com.cheng.spring.service.BuyPhoneService;
public class BuyPhoneServiceImpl implements BuyPhoneService{
@Override
public void giveMeAphone(String name, String pass) {
System.out.println("我的身份只有8848钛金手机才配得上,8848你值得拥有");
}
@Override
public void newMethod() {
System.out.println("甩卖8848,只要998");
}
}
我把新加的方法也运行一下
public class Test {
public static void main(String[] args) {
//通过context模块加载配置文件,获取bean实例
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获得代理工厂返回的接口
BuyPhone buyPhone = (BuyPhone)context.getBean("factoryBean");
buyPhone.giveMeAphone("此成", "123");
buyPhone.newMethod();
}
}
结果:
我本来只想要giveMeAphone方法用这个环绕通知的,但这个实现类的所有方法都加上这个通知了,这并不是我想要的。
2 配置RegexMethodPointcutAdvisor
在spring aop 的support里有这个帮助类,帮助我们实现单个或多个指定方法的拦截(织入)
它相当于是一个过渡类,把目标对象的id先放到这个RegexMethodPointcutAdvisor的active属性内,再给patterns赋值告诉它你只需要什么方法被织入通知。最后把这个Bean的id再给原来的代理工厂,即可实现指定方法的切入。
<?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:p="http://www.springframework.org/schema/p"
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">
<!-- 配置目标对象-->
<bean id="buyPhoneService" class="com.cheng.spring.serviceImpl.BuyPhoneServiceImpl"/>
<!-- 配置切面:环绕通知 -->
<bean id="buyPhoneBeforeAdvice" class="com.cheng.spring.advice.BuyPhoneBeforeAdvice"/>
<!-- 使用RegexpMethodPointcutAdvisor指定切入的是哪个方法 -->
<!--patterns,如果有多个指定的值的话,可以使用,隔开,例如value=".*giveMeAphone,.*newMethod"-->
<bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="buyPhoneAroundAdvice"
p:patterns=".*giveMeAphone"/>
<!-- 使用代理工厂将切面织入目标对象 -->
<bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.cheng.spring.service.BuyPhoneService"
p:target-ref="buyPhoneService"
p:interceptorNames="buyPhoneAroundAdvice"
p:proxyTargetClass="false"/>
</beans>
再次运行一下:
完成我想要的单个方法的织入。
3 配置BeanNameAutoProxyCreator
它是帮助我们减少配置一堆代理工厂的类
比如说我上面配置的一个代理工厂的Bean,它只能帮助我把一个/多个切面织入到另一个目标对象中,如果我想多个目标对象都用这个切面,我就要配置多个代理工厂,这就造成了代码的重复。而且我要获得生产好的代理返回的Bean,还需要去配置里找代理工厂的id,实在有些麻烦。
使用BeanNameAutoProxyCreator可以方便的获得代理后的目标对象接口
这个类在Spring aop包中的framework中的autoproxy中
BeanNameAutoProxyCreator的属性:
- p:beanNames=“你需要切入的主业务bean id” (可以写*Service,将自动找到以Service结尾的bean id)
- p:interceptorNames=“切面id”
使用BeanNameAutoProxyCreator后,我们的配置变成这样
<?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:p="http://www.springframework.org/schema/p"
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">
<!-- 配置切入点-->
<bean id="buyPhoneService" class="com.cheng.spring.serviceImpl.BuyPhoneServiceImpl"/>
<!-- 配置切入面:环绕通知 -->
<bean id="buyPhoneAroundAdvice" class="com.cheng.spring.advice.BuyPhoneAroundAdvice"/>
<!-- 使用RegexpMethodPointcutAdvisor指定切入的是哪个方法 -->
<!--patterns,如果有多个指定的值的话,可以使用,隔开,例如value=".*giveMeAphone,.*newMethod"-->
<bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="buyPhoneAroundAdvice"
p:patterns=".*giveMeAphone"/>
<!-- 自动返回代理工厂 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
p:beanNames="*Service"
p:interceptorNames="advisor"/>
</beans>
还是运行之前的主函数:
可以看到结果是一致的。
通过BeanNameAutoProxyCreator,我们可以配置多个目标对象来织入我们需要的切面。
4 总结
- ProxyFactoryBean :代理工厂,最经典基础的aop的配置,只使用它的话,会让切面(通知)织入到目标对象的所有方法内;
- RegexMethodPointcutAdvisor:方法切入,配合ProxyFactoryBean 使用,可以只让切面(通知)织入到目标对象的某几个方法内;
- BeanNameAutoProxyCreator:自动创建代理对象,如果需要一个切面织入到多个目标对象中,它是很棒的选择。
下一篇:Spring学习(9)-AOP之使用aop:config标签
文毕,如有助,赞之吾嗨~