2、spring之aop 原理和应用

Aspect Oriented Progroming 

目标Target:就是通知织入的类主体。

连接点JoinPiont:每个方法都是一个连接点(方法调用前、方法调用后、方法异常、方法前后)

切点pointCut:并不是所用连接点都要织入通知,织入通知的连接点称为切点。

引介Introdution:特殊的通知,为目标类增加属性和方法。

通知Advice:通知是切点上织入的一段代码,(方法前通知、方法后通知、异常通知、前后环绕通知、引介)。

织入Weaving:将通知织入切点的过程;spring采用动态代理的方式,Aspectj采用编译期和类装载织入。

代理Proxy:代理类可能是与原类同接口的类,也可以是原类的子类。

demo1基于java类的前置通知

package org.nick.test;
import org.springframework.aop.framework.ProxyFactory;
public class Test {
	public static void main(String[] str){
		//为目标类Waiter织入通知
		//创建目标对象
		Waiter target = new Waiter();
		//创建通知类实例
		MyBeforeMethod advice = new MyBeforeMethod();
		//创建代理对象,接管目标对象
		//这是spring提供的代理工厂类
		ProxyFactory proxy = new ProxyFactory();
		//设置要代理的目标类
		//proxy将接管target的所用操作
		proxy.setTarget(target);
		//为代理对添加前置通知
		proxy.addAdvice(advice);
		//将设置好通知的代理对象返回给目标对象
		target = (Waiter) proxy.getProxy();
		//执行原有的方法
		//这里是每个方法都设置的通知
		target.sayHello("nick");
        		
	}
}


package org.nick.test;
public class Waiter {
	public void sayHello(String name){
		System.out.print("欢迎光临:"+name);
	}
	public void sayBye(String name){
		System.out.println("下次再来:"+name);
	}
}


package org.nick.test;
import java.lang.reflect.Method;
import org.apache.commons.httpclient.methods.GetMethod;
import org.springframework.aop.MethodBeforeAdvice;
public class MyBeforeMethod implements MethodBeforeAdvice {
	//Method   目标类的方法名
	//arg1     切点方法的参数
	//Object   目标类
	@Override
	public void before(Method arg0, Object[] arg1, Object arg2)
			throws Throwable {
		//获取切点方法参数
		System.out.println("下午好:"+arg1[0]);
		//获取切点的方法名
		System.out.println("切点方法名:"+arg0.getName());	
		//获取目标类
		System.out.println("目标类名:"+arg2.toString());	
		
	}
}

print:

下午好:nick
切点方法名:sayHello
目标类名:org.nick.test.Waiter@51a23566
欢迎光临:nick

demo2基于beans.xml的前置通知 p73

关键:用一个接口接收代理对象,不然会报com.sun.proxy.$Proxy0 cannot be cast to org.nick.test.Person异常

           如果用类来接收,就需要在proxy下设置:p:proxyTargetClass =”true”   这样就不用实现BasePerson接口了,同时将使用CGLIB的代理方法而不是sun

的代理方法,这两种代理方法在创建对象和代理对象运行效率上存在差异,一般将singleton对象设置为CGLIB代理,加快其运行效率。

            使用ProxyFactoryBean来创建代理对象实例,而java中使用的是ProxyFactory,实际上ProxyFactoryBean使用ProxyFactory方法创建的;

使用 p:interceptorNames 表示通知.类型可以是String[]型的,自动根据类型判定是前置或后置通知。p75

        和p:interfaces这里可以是多个接口,用<list><value>标签

<?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: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-3.1.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-3.1.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">    
   <bean id="target" class="org.nick.test.Person"></bean>
   <bean id="advice" class="org.nick.test.MyBeforeMethod"></bean> 
   <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"
   p:interfaces="org.nick.test.BasePerson"
   p:target-ref="target"
   p:interceptorNames="advice"
   />
</beans>


package org.nick.test;
public interface BasePerson {
	public void sayHello(String name);
	public void sayBye(String name);
}


package org.nick.test;
public class Person implements BasePerson {
	@Override
	public void sayHello(String name) {
		System.out.println("你好:"+name);
	}
	@Override
	public void sayBye(String name) {
		System.out.println("再见:"+name);
	}
}


package org.nick.test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
	public static void main(String[] str){
		//加载beans.xml文件
		ApplicationContext ac = new ClassPathXmlApplicationContext("conf/beans.xml");
		BasePerson skx =(BasePerson) ac.getBean("proxy");
		skx.sayHello("skx");
	}
}<span style="color:#ff0000;">
</span>

后置通知:实现AfterReturningAdvice接口

环绕通知:实现MethodInterceptor接口

唯一方法;invoke(MethodInvocation invocation)

Object[] args = invocation.getArguments();获取切点方法的参数,入参

Object obj  = invocation.proceed();

retrun obj;

在invocation.proceed()前的code在切点方法前执行,在之后的code在切点方法之后执行。

异常通知:实现ThrowsAdvice接口

自定义方法:必须名为afterThrowing(Method method,Object[] args,Object obj,Exception ex){}         切点方法、切点方法参数、目标对象、异常

                      或afterThrowing(Exception ex){}

前面都是对一个对象的所有方法进行了织入,实际中只需要对指定的方法作为切点:切点切面—>PointcutAdvisor

**************切点:对连接点定位,有选择性的对目标方法进行织入*********************

定位:

通过ClassFilter定位目标类  p79

通过MethodMatcher定位目标方法

切面:通知+切点

1、静态方法切点:字符串匹配方法切点  &  正则表达式匹配方法切点

1.1、字符串匹配方法切点 继承StaticMethodMatcherPointcutAdvisor 

现只对Waiter的sayBye方法进行织入:

现要对  目标对象、通知、切面、代理对象 进行配置

<bean id="target" class="org.nick.test.Waiter"></bean>    
   <bean id="advice" class="org.nick.test.MyBeforeMethod"></bean> 
      <!-- 切面是需要通知code,除了通知code也可以陪着target和class/interfces信息的,一般在proxy那里配置-->
   <bean id="advisor" class="org.nick.test.StaticStringPointCut"
    p:advice-ref="advice"
   /> 
   <!-- 引用切面,配置interceptorNames="切面"而不是通知了 -->
   <!-- "P:proxyTargetClass" associated with an element type "bean" is not bound. -->
   <bean id="proxy"  class="org.springframework.aop.framework.ProxyFactoryBean"
    p:interceptorNames="advisor"
    p:target-ref="target"
    p:proxyTargetClass="true"/>


package org.nick.test;
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
public class StaticStringPointCut extends StaticMethodMatcherPointcutAdvisor {
	@Override
	public boolean matches(Method arg0, Class<?> arg1) {
		return  "sayBye".equals(arg0.getName());
	}
      //默认情况下,匹配所有类下的方法,这里增加一个ClassFilter只匹配Waiter下的方法
	public ClassFilter getClassFilter(){
		//复写其中的matches方法
		return new ClassFilter(){
			public boolean matches(Class clazz){
				// Class1.isAssignableFrom(Class2)   
				//是用来判断一个类Class1和另一个类Class2是否相同或是另一个类的子类或接口。
				return Waiter.class.isAssignableFrom(clazz);
			}
		};
	}
}


package org.nick.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test3 {
	public static void main(String[] args) {
		//加载beans.xml文件
				ApplicationContext ac = new ClassPathXmlApplicationContext("conf/beans.xml");
				Waiter skx =(Waiter) ac.getBean("proxy");
				skx.sayHello("skx");
				skx.sayBye("skx");
	}
}

print:

欢迎光临:skx
下午好:skx
切点方法名:sayBye
目标类名:org.nick.test.Waiter@4dea0aa2
下次再来:skx

这里就只对sayBye进行了织入;

1.2、正则表达式匹对方法:使用字符串匹对类和方法,书写的code较多,使用正则表达只需要配置xml就可以完成定位

      p83

 <!-- 正则匹配方法 -->
   <bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
    p:advice-ref="advice">
    <property name="patterns">
    	<list>
    		<value>.*Hello.*</value>
    	</list>
    </property>
   </bean>
   <!-- 引用切面,配置interceptorNames="切面"而不是通知了 -->
   <!-- "P:proxyTargetClass" associated with an element type "bean" is not bound. -->
   <bean id="proxy"  class="org.springframework.aop.framework.ProxyFactoryBean"
    p:interceptorNames="regexAdvisor"
    p:target-ref="target"
    p:proxyTargetClass="true"/>

2、自动创建代理BeanPostProcessor

上面的每个对象都要创建一个代理对象,显得比较麻烦,这里将引入自动创建代理。

实现类Bean

2.1、BeanNameAutoProxyCreator基于Bean配置的命名规则自动代理器,可以同时代理多个必须使用CGLIB代理

<bean id="target" class="org.nick.test.Waiter"></bean>    
   <bean id="advice" class="org.nick.test.MyBeforeMethod"></bean> 
   <!--可以匿名创建-->
   <bean id = "atuoProxy" class = "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
      p:beanNames="*target"
      p:interceptorNames = "advice"
      />

	public static void main(String[] args) {
		//加载beans.xml文件
				ApplicationContext ac = new ClassPathXmlApplicationContext("conf/beans.xml");
				/<span style="color:#ff0000;">/注意这里是直接将target取出来,就已经是代理好了的对象了</span>
				Waiter skx =(Waiter) ac.getBean("target");
				skx.sayHello("skx");
				skx.sayBye("skx");
	}
}

cglib自动创建代理:

 <bean id="target" class="org.nick.test.Waiter"></bean>  
   <bean id="ptarget" class="org.nick.test.Person"></bean>      
   <bean id="advice" class="org.nick.test.MyBeforeMethod"></bean> 
   <bean id = "atuoProxy" class = "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
      p:beanNames="*target"
      p:interceptorNames = "advice"
      p:optimize="true"
      />


public static void main(String[] args) {
		//加载beans.xml文件
				ApplicationContext ac = new ClassPathXmlApplicationContext("conf/beans.xml");
				//注意这里是直接将target取出来,就已经是代理好了的对象了
				Waiter skx =(Waiter) ac.getBean("target");
				Person nick =(Person) ac.getBean("ptarget");
				skx.sayHello("skx");
				nick.sayBye("nick");
	}

2.2、DefaultAdvisorAutoProxyCreator
基于切面Advisor的自动创建代理器

<bean id="target" class="org.nick.test.Waiter"></bean>  
   <bean id="ptarget" class="org.nick.test.Person"></bean>      
   <bean id="advice" class="org.nick.test.MyBeforeMethod"></bean> 
   <bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
    p:advice-ref="advice"
    p:patterns=".*Hello.*"
   />
   <!-- 自动加载切面对象 -->
   <bean id = "atuoProxy" class = "org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
   p:optimize="true"
   />


public static void main(String[] args) {
				ApplicationContext ac = new ClassPathXmlApplicationContext("conf/beans.xml");
				Waiter skx =(Waiter) ac.getBean("target");
				Person nick =(Person) ac.getBean("ptarget");
				skx.sayHello("skx");
				nick.sayHello("nick");
				skx.sayBye("skx");
				nick.sayBye("nick");
	}

print:

下午好:skx
切点方法名:sayHello
目标类名:org.nick.test.Waiter@6467b8ff
欢迎光临:skx
下午好:nick
切点方法名:sayHello
目标类名:org.nick.test.Person@690a614
你好:nick
下次再来:skx
再见:nick

切面正确加载!只对*hello方法织入

基于AspectJ和基于schema文件的切面暂不讲p88

    原文作者:AOP
    原文地址: https://blog.csdn.net/nickfover/article/details/42065251
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞