Spring -- aop(面向切面编程),前置&后置&环绕&抛异常通知,引入通知,自动代理
1.概要
aop:面向方面编程.不改变源代码,还为类增加新的功能.(代理) 切面:实现的交叉功能. 通知:切面的实际实现(通知要做什么,怎么做). 连接点:应用程序执行过程期间,可以插入切面的地点. 切入点:真正的将通知应用到目标程序中的地点,一定是连接点.切入点是连接点的子集. 切入点决定了一个特定的类的特定方法是否满足一定的规则 引入:为类增加新的属性和方法. (引入通知) 目标对象:被通知的对象. 代理:把通知应用到目标对象以后,产生新的对象,该对象就称为代理对象. 织入:创建代理对象过程. 编译期织入:.java --> .class,需要特殊的编译器. 类装载期织入:将java字节码载入到jvm时,将通知织入.需要特殊的classloader. 运行期(runtime): cglib: aop alliance:aop联盟. spring代理方案: 1.接口代理:jdk动态代理,创建更加松耦合的系统. 2.对类代理:cglib代理,final方法无法被代理. spring aop编程: 1.aop alliance.jar(已经集成在spring.jar中) + cglib. ${spring解压目录}/lib/cglib/*.jar 2.加入aspectj类库 ${{spring解压目录}/lib/aspectj/*.jar(aspectjrt.jar + aspectjweaver.jar) 3.创建接口和实现类 public interface WelcomeService { public void sayName(); } /** * 目标类 */ public class WelcomeServiceImpl implements WelcomeService { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void sayName() { System.out.println(name); } } 4.创建前置通知. /** * 前置通知(方法前通知) */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("hello world"); } } 5.配置文件. <?xml version="1.0"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- 前置通知(方法前通知) --> <bean id="myMethodBeforeAdvice" class="cn.itcast.spring.aop.advice.MyMethodBeforeAdvice" /> <!-- 目标对象 --> <bean id="welcomeServiceTarget" class="cn.itcast.spring.aop.service.WelcomeServiceImpl"> <property name="name" value="tom" /> </bean> <!-- 代理对象 --> <bean id="welcomeService" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 代理接口集 --> <property name="proxyInterfaces"> <list> <value>cn.itcast.spring.aop.service.WelcomeService</value> </list> </property> <!-- 拦截器名集 --> <property name="interceptorNames"> <list> <value>myMethodBeforeAdvice</value> </list> </property> <!-- 指定目标对象 --> <property name="target" ref="welcomeServiceTarget" /> </bean> </beans> 6.App ApplicationContext ac = new ClassPathXmlApplicationContext( "cn/itcast/spring/aop/aop.xml"); WelcomeService ws = (WelcomeService) ac.getBean("welcomeServiceTarget"); ws.sayName(); public interface Pointcut{ ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); } Pointcut:切入点 Advice:通知 Advisor:切入点通知,组合体,既包含通知又包含切入点.对原来的通知的包装,增加定义切入点功能 PointcutAdvisor{ Pointcut getPointcut(); Advice getAdvice(); } 引入通知: 1.定义引入通知. Dao:data access object.数据(数据库的表数据)访问对象. dto:data transfer object,数据传输对象. struts1(actionform jsp --> action) 集成dao. 1.引入数据源类库 ${spring解压目录}/lib/c3p0/*.jar c3p0-0.9.1.2.jar 2.配置spring配置文件,链接数据源
2. AOP介绍
1.切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
2.连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
3.通知:切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
4.切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。
5.引入:为类添加新方法和属性。
6.目标对象:被通知的对象。既可以是你编写的类也可以是第三方类。
7.代理:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
8.织入:将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
编译期:切面在目标对象编译时织入.这需要一个特殊的编译器.
类装载期:切面在目标对象被载入JVM时织入.这需要一个特殊的类载入器.
运行期:切面在应用系统运行时织入.
spring有两种代理方式:
1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。该类让spring动态产生 一个新类,它实现了所需的接口,织入了通知,并且代理对目标对象的所有请求。
2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。使用该方式时需要注意:
1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
spring实现了aop联盟接口。
spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。
简单的ClassFilter接口实现-ClassFilter.TRUTE。它是规范的适合任何类的ClassFilter实例,适合用于只根据方法决定时候符合要求的切入。ClassFilter通过类过滤切面,MethodMatcher通过方法过滤切面。
public interface MethodMatcher{
boolean matches(Method m,Class targetClass);1.
boolean isRuntime();2.
boolean matchers(Method m,Class target,Object[] args);3.
}
1. 根据目标类和方法决定方法是否被通知。因为可以静态的判断,所以可以在AOP代理被创建时候调用一次这个方法。该方法的结果最终决定了通知是否被织入。
如果1.返回true,2.被调用来决定MethodMatcher的类型。有两种类型:静态和动态。静态切入点的意思是通知总是被执行。如果一个切入点是静态的,该方法返回false.动态切入点根据运行时方法的参数值决定通知是否需要执行。如果切入点是动态的,该方法返回true。和1.方法类似,该方法也是在代理创建时运行一次。
如果切入点是静态的,3.永远不会执行,对于动态切入点,需要根据运行时的参数决定方法是否被通知,所以会增加系统的负担,尽量使用静态切入点。
正则表达式切入点 RegexpMethodPointcut
使用spring的静态切入点
<bean id="xxxTarget" class="xxxServiceImpl"/> <bean id="xxxAdvice" class=""/> <bean id="xxxAdvisor" class="…RegExpPointcutAdvisor"> <property name="pattern"> <value>.*get.+bar.+<value> </property> <property name="advice"> <ref bean="xxxAdvice" /> </property> </bean> <bean id="xxxService" class="…ProxyFactoryBean"> <property name="proxyInterfaces"> <value>……xxxService</value> </property> <property name="interceptorNames"> <list><value>xxxAdvisor</value></list> </property> <property name="target"><ref bean="xxxTarget" /></property> </bean> 该规则表示任何类的以get开头,后面至少有一个字符,然后跟着by,后面至少有一个字符的方法
使用动态切入点
<bean id="xxxTarget" class="xxxServiceImpl"/> <bean id="xxxAdvice" class=""/> <bean id="xxxPointcut" class="…ControlFlowPointcut"> <contructor-arg"> <value>javax.servlet.http.HttpServlet</value> </contructor-arg> </bean> <bean id="xxxAdvisor" class="…DefaultPointcutAdvisor"> <property name="advice"> <ref bean="xxxAdvice" /> </property> <property name="pointcut"> <ref bean="xxxPointcut"> </property> </bean> <bean id="xxxService" class="……ProxyFactoryBean"> …… </bean>
有许多类需要通知时,显式的创建每个代理就会显得很笨拙。spring有一个自动代理机制,它可以让容器为我们产生代理。
BeanNameAutoProxyCreator DefaultAdvisorAutoProxyCreator
示例代码:
service层代码:接口及接口实现
WelcomeService.java 接口1
public interface WelcomeService { public void sayName(); }
WelcomeService2.java 接口2
public interface WelcomeService2 { public void sayName2(); }
ModifyDate.java 接口3
public interface ModifyDate { public void setModifyDate(Date date); public Date getModifyDate(); }
WelcomeServiceImpl.java 接口实现
public class WelcomeServiceImpl implements WelcomeService,WelcomeService2 { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void sayName() { System.out.println(name); // String str = null ; //制造异常 // str.toCharArray(); } public void sayName2() { System.out.println("kkk"); } }
advice, 通知
MyMethodBeforeAdvice.java 前置通知
public class MyMethodBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("before"); } }
MyAfterReturningAdvice.java 后置通知
public class MyAfterReturningAdvice implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("after"); } }
MyMethodInterceptor.java 环绕通知
public class MyMethodInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation arg0) throws Throwable { System.out.println("begin"); //调用目标对象的方法 Object res = arg0.proceed(); System.out.println("end"); return res; } }
MyThrowsAdvice.java 异常通知
public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(Method m,Object[] os,Object target,Throwable throwable){ System.out.println(m); System.out.println(target); System.out.println("出事了!" + throwable.toString()); } }
MyDII.java 引入通知
/** * 引入通知(代理引入拦截器) */ public class MyDII extends DelegatingIntroductionInterceptor implements ModifyDate { private static final long serialVersionUID = 1926256070698432626L; private Date modifyDate ; public Date getModifyDate() { return modifyDate; } public void setModifyDate(Date date) { this.modifyDate = date ; } }
App.java 测试程式
public class App { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext( "cn/itcast/spring/aop/aop.xml"); WelcomeService ws = (WelcomeService) ac.getBean("welcomeService"); ws.sayName(); WelcomeService2 ws2 = (WelcomeService2) ws; ws2.sayName2(); ((ModifyDate)ws).setModifyDate(new Date()); //相当于实现多重继承 System.out.println(((ModifyDate)ws).getModifyDate()); } }
aop.xml 配置文件
<?xml version="1.0"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- 前置通知(方法前通知) --> <bean id="myMethodBeforeAdvice" class="cn.itcast.spring.aop.advice.MyMethodBeforeAdvice" /> <!-- 后置通知(方法返回后通知) --> <bean id="myAfterReturningAdvice" class="cn.itcast.spring.aop.advice.MyAfterReturningAdvice" /> <!-- 环绕通知(方法拦截器) --> <bean id="myMethodInterceptor" class="cn.itcast.spring.aop.advice.MyMethodInterceptor" /> <!-- 环抛出异常知--> <bean id="myThrowsAdvice" class="cn.itcast.spring.aop.advice.MyThrowsAdvice" /> <!-- 引入通知(代理引入拦截器) --> <bean id="myDII" class="cn.itcast.spring.aop.advice.MyDII" /> <!-- 名称匹配方法切入点通知(对原来的通知的包装,增加定义切入点功能) --> <bean id="beforeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <property name="advice" ref="myMethodBeforeAdvice" /> <property name="mappedNames"> <list> <value>sayName</value> </list> </property> </bean> <!-- 后置通知包装 --> <bean id="afterAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <property name="advice" ref="myAfterReturningAdvice" /> <property name="mappedNames"> <list> <value>sayName2</value> </list> </property> </bean> <!-- 默认的引入切入点通知(类似于名称匹配方法切入点通知) --> <bean id="defaultIntroductionAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"> <constructor-arg ref="myDII" /> </bean> <!-- 目标对象 --> <bean id="welcomeServiceTarget" class="cn.itcast.spring.aop.service.WelcomeServiceImpl"> <property name="name" value="tom" /> </bean> <!-- 代理对象, 不推荐使用proxyTargetClass <bean id="welcomeService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <list> <value>cn.itcast.spring.aop.service.WelcomeService</value> <value>cn.itcast.spring.aop.service.WelcomeService2</value> <value>cn.itcast.spring.aop.service.ModifyDate</value> </list> </property> <property name="interceptorNames"> <list> <value>beforeAdvisor</value> <value>afterAdvisor</value> <value>myMethodInterceptor</value> <value>myThrowsAdvice</value> <value>defaultIntroductionAdvisor</value> </list> </property> <property name="target" ref="welcomeServiceTarget" /> <property name="proxyTargetClass" value="true" /> </bean> --> <!-- bean名自动代理创建器,按照bean的名称自动创建代理对象 <bean id="beanNameAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <value>beforeAdvisor</value> <value>afterAdvisor</value> <value>myMethodInterceptor</value> <value>myThrowsAdvice</value> <value>defaultIntroductionAdvisor</value> </list> </property> <property name="beanNames"> <list> <value>*ServiceTarget</value> </list> </property> </bean> --> <!-- 默认的切入点通知自动代理创建器(将所有的advisor应用到所有的业务bean上去,能否加通 知,要看是否满足切入点的要求) --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" /> </beans>