浅析spring中的AOP(面向切面编程)

来源:互联网 发布:淘宝erp系统 编辑:程序博客网 时间:2024/05/16 03:36

* 它用来生成代理对象
 * 它需要所有的参数
 * * 目标对象
 * * 增强
 * @author cxf
 */
/**
 * 1. 创建代理工厂
 * 2. 给工厂设置三样东西:
 *   * 目标对象:setTargetObject(xxx);
 *   * 前置增强:setBeforeAdvice(该接口的实现)
 *   * 后置增强:setAfterAdvice(该接口的实现)
 * 3. 调用createProxy()得到代理对象
 *   * 执行代理对象方法时:
 *   > 执行BeforeAdvice的before()
 *   > 目标对象的目标方法
 *   > 执行AfterAdvice的after()
 * @author cxf
 *
 */
public class ProxyFactory {
private Object targetObject;//目标对象
private BeforeAdvice beforeAdvice;//前置增强
private AfterAdvice afterAdvice;//后置增强


/**
* 用来生成代理对象
* @return
*/
public Object createProxy() {
/*
* 1. 给出三大参数
*/
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = targetObject.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*
* 在调用代理对象的方法时会执行这里的内容
*/
// 执行前置增强
if(beforeAdvice != null) {
beforeAdvice.before();
}

Object result = method.invoke(targetObject, args);//执行目标对象的目标方法
// 执行后置增强
if(afterAdvice != null) {
afterAdvice.after();
}

// 返回目标对象的返回值
return result;
}
};
/*
* 2. 得到代理对象
*/
Object proxyObject = Proxy.newProxyInstance(loader, interfaces, h);
return proxyObject;
}


public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
}



// 服务员接口
public interface Waiter {
// 服务
public void serve();

public void shouQian();
}


// 服务员实现类

public class ManWaiter implements Waiter {
public void serve() {
System.out.println("服务中...");
}

public void shouQian() {
System.out.println("收钱!");
}
}


// 前置增强接口

public interface BeforeAdvice {
public void before();
}


// 后置增强接口

public interface AfterAdvice {
public void after();
}

/*
 * 目标是让目标对象和增强都可以切换!
 */
public class Demo3 {
@Test
public void fun1() {
ProxyFactory factory = new ProxyFactory();//创建工厂
factory.setTargetObject(new ManWaiter());//设置目标对象
factory.setBeforeAdvice(new BeforeAdvice() {//设置前置增强
public void before() {
System.out.println("您好!");
}
});

factory.setAfterAdvice(new AfterAdvice() {//设置后置增强
public void after() {
System.out.println("再见!");
}
});

Waiter waiter = (Waiter)factory.createProxy();
waiter.shouQian();
waiter.serve();
}




1、AOP 是什么?

都知道面向对象无非是封装、继承、多态,根据某一个类,实例化一个对象,然后操作这一个对象。

AOP  按我自己的理解就是,面向多个对象,或是面向N个对象。

比如我们在service中,可能要对所有的DML操作添加一个transaction,如果说service很少的话,我们可以直接在service中添加一个transaction,那么如果再开发中,有几十个甚至几百个service都需要给他们添加一个transaction,难道我们就写上几十遍、上百遍的transaction。当然不是,在spring中,提供了一个AOP,面向切面的编程,说白了就是面向N个对象的编程,就是将这所有的service中的共同部分提取出来,比如将所有的transaction提取出来,写成一个通知,然后织入(注意别写错字),织入action的代理对象之中,这个actionproxy会先执行织入的通知,然后执行其他。

2、首先明白几个概念(概念可以先略过,还是先后面代码,再来明白吧),

代理模式:代理模式的英文叫做ProxySurrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

Aspect(切面):是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容---它的功能、在何时和何地完成其功能

joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.

Pointcut(切入点):所谓切入点是指我们要对哪些joinpoint进行拦截的定义.

   通知定义了切面的”什么”和”何时”,切入点就定义了”何地”.

Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Target(目标对象):代理的目标对象

Weaving(织入):是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象

Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.


spring在运行期创建代理,不需要特殊的编译器.

spring有两种代理方式:

1.若目标对象实现了若干接口,spring使用JDKJava.lang.reflect.Proxy类代理。

2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

   使用该方式时需要注意:

          1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。

             对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,

            这种方式应该是备用方案。

          2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要

             被通知的方法都被复写,将通知织入。final方法是不允许重写的。

   spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了

  封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的

  结果。



3、概念可以先略过。下面写个简单的,来描述下(前置通知)。

比如实现一个功能,就是在所有请求到达action之前为这个请求添加一个日志(先导入spring的必备的jar包),下面的5段代码放在同一个包中即可运行,直接用Java application运行就行

* com.springsource.NET.sf.cglib-2.2.0.jar
cglib代理
* com.springsource.org.aopalliance-1.0.0.jar
* com.springsource.org.aspectj.tools-1.6.6.RELEASE.jar
* org.springframework.aop-3.0.2.RELEASE.jar
spring的面向切面编程,提供AOP(面向切面编程)实现
* org.springframework.aspects-3.0.2.RELEASE.jar
spring提供对AspectJ框架的整合


定义接口

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. TestInterface1  

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.niit.spring.aop;  
  2.   
  3. /** 
  4.  * @author Emine_wang 
  5.  */  
  6. public interface TestInterface1 {  
  7.     public void sayHello();  
  8.     public void sayh();  
  9.   
  10. }  

实现:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. TestInterface1Impl   

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.niit.spring.aop;  
  2.   
  3. /** 
  4.  * @author Emine_wang 
  5.  */  
  6. public class TestInterface1Impl implements TestInterface1 {  
  7.   
  8.     @Override  
  9.     public void sayHello() {  
  10.         // TODO Auto-generated method stub  
  11.           
  12.         System.out.println("Hello Emine");  
  13.   
  14.     }  
  15.     public void sayh(){  
  16.           
  17.         System.out.println("Hello Wang");  
  18.     }  
  19. }  


通知(就是概念中的Advice):

MyMethodBeforeAdvice.java

别忘了继承org.springframework.aop.MethodBeforeAdvice;需要导包


[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.niit.spring.aop;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. import org.springframework.aop.MethodBeforeAdvice;  
  6.   
  7. /** 
  8.  * @author Emine_wang 
  9.  *  编写通知,advice 
  10.  */  
  11. public class MyMethodBeforeAdvice implements MethodBeforeAdvice{  
  12.     @Override  
  13.     public void before(Method method, Object[] args, Object object)  
  14.             throws Throwable {  
  15.         /** 
  16.          * method指表示被调用的方法 
  17.          * args给这个method方法传递的参数 
  18.          * object目标对象 
  19.          * 其实是利用反射机制得到方法中的名字、参数等信息 
  20.          *  
  21.          */  
  22.         System.out.println("记录日志"+method.getName());  
  23.     }  
  24.       
  25. }  


配值spring的配置文件beans.xml

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:context="http://www.springframework.org/schema/context"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  6.     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  7.     http://www.springframework.org/schema/context  
  8.     http://www.springframework.org/schema/context/spring-context-3.0.xsd">  
  9.       
  10.     <!-- 配置目标对象(被代理对象) -->  
  11.     <bean id="TestInterface1Impl" class="com.niit.spring.aop.TestInterface1Impl">  
  12.     </bean>  
  13.       
  14.     <!-- 配置前置通知 -->  
  15.     <bean id="MyMethodBeforeAdvice" class="com.niit.spring.aop.MyMethodBeforeAdvice"></bean>  
  16.       
  17.     <!-- 配置代理对象 -->  
  18.       
  19.     <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">   
  20.       
  21.         <!-- 配置代理的接口集 -->  
  22.         <property name="proxyInterfaces">  
  23.             <list>  
  24.                 <value>com.niit.spring.aop.TestInterface1</value>  
  25.             </list>  
  26.         </property>  
  27.           
  28.         <!-- 把通知织入到代理对象 -->  
  29.           
  30.         <property name="interceptorNames">  
  31.         <!-- 相当于把前置通知 MyMethodBeforeAdvice和代理对象关联起来,我们也可以把通知看成是拦截器  
  32.         struts2核心拦截器  
  33.         -->  
  34.             <value>MyMethodBeforeAdvice</value>  
  35.         </property>  
  36.           
  37.         <!-- 配置被代理对象,没有被代理对象,那么context.getBean("proxyFactoryBean")为空 -->  
  38.           
  39.         <property name="target" ref="TestInterface1Impl"></property>  
  40.           
  41.       
  42.      </bean>  
  43.       
  44.       
  45.       
  46.       
  47.       
  48.       
  49. </beans>  

测试:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.niit.spring.aop;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. import com.niit.spring.bean.User;  
  7.   
  8. public class TestAop {  
  9.   
  10.     /** 
  11.      * @param Emine_Wang 
  12.      */  
  13.     public static void main(String[] args) {  
  14.         // TODO Auto-generated method stub  
  15.           
  16.         ApplicationContext  context = new ClassPathXmlApplicationContext("com/niit/spring/aop/beans.xml");//加载beans配置文件  
  17.           
  18. //      TestInterface1Impl testInterface1Impl =  (TestInterface1Impl) context.getBean("TestInterface1Impl");//这样写相当于没有用到代理对象,当然不会执行通知了  
  19. //      testInterface1Impl.sayHello();  
  20.         TestInterface1 testInterface1 = (TestInterface1)context.getBean("proxyFactoryBean");//反射生成代理对象  
  21. //      System.out.println(testInterface1.getClass());  
  22.           
  23.         testInterface1.sayHello();  
  24.         testInterface1.sayh();  
  25.     }  
  26.   
  27. }  

结果:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).  
  2. log4j:WARN Please initialize the log4j system properly.  
  3. 记录日志sayHello  
  4. Hello Emine  
  5. 记录日志sayh  
  6. Hello Wang  

我们会发现,我并没有在TestInterface1Impl中的sayHello()和sayh()方法中打印”记录日志“,却发现这里打印了。说明,通知advice已经被执行,是由代理对象帮我们执行。

0 0