Spring的AOP思想研究和实现

来源:互联网 发布:sql between 编辑:程序博客网 时间:2024/05/17 03:36

AOP思想在Spring中使用最多的是事务管理。在没有AOP之前的时候都是使用OOP的思想按照纵向的方向来编程。哪里需要事务,哪里不需要,需要什么样的事务。因此,导致了很多繁琐的操作。现在AOP思想面向切面编程。事务管理就是J2EE应用中一个横切多个对象的横切关注点的例子。

AOP面向切面编程,可用于权限验证,效率检查,事务,异常管理等

事务的处理一般有两种模式:依赖特定事务资源的事务处理与依赖容器的参数化事务管理

针对第二种依赖Spring容器来参数化事务管理。

Spring框架中所提供的AOP支持,是基于动态AOP机制实现的,即通过JDK动态代理模式,在目标对象的方法调用前后插入相应的处理代码。AOP代理可以是基于JDK动态代理,也可以是基于CGLIB代理。Spring默认使用的是基于Java Dynamic Proxy模式实现,这样任何的接口都能被代理。基于Spirng框架的应用程序开发,程序员会有一种自然的倾向性来实现面向接口编程而不是类,业务对象通常也是实现一个或者多个接口,这也是一种良好的编程习惯。Spring也可以基于CGLIB实现AOP代理,这样所代理的是类而不是接口。如果一个业务对象没有实现某一个接口,那么CGLIB将被使用。

总结点就是:AOP基于Java Dynamic Proxy是面向接口

                     AOP基于CGLIB面向的是类

现在来分析一下JDK动态代理的原理。

JDK proxy invocationHanlder

 JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。

   而Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例。

User类

package model;

public class User {

         privateint id;

         privateString name;

 

 

         publicint getId() {

                   returnid;

         }

 

 

         publicvoid setId(int id) {

                   this.id= id;

         }

 

 

         publicString getName() {

                   returnname;

         }

 

 

         publicvoid setName(String name) {

                   this.name= name;

         }

 

 

}

UserDao类

package dao;

 

 

import model.User;

 

 

public interface UserDao{

  public void save(User user);

  public void delete(User user);

}

UserDaoImpl类

package DaoImpl;

import model.User;

import dao.UserDao;

 

 

public class UserDaoImpl implementsUserDao{

 

 

         @Override

         publicvoid save(User user) {

                   System.out.println("保存用户");   

                  

         }

 

 

         @Override

         publicvoid delete(User user) {

                   //TODO Auto-generated method stub

                   System.out.println("删除用户");

         }

 

 

}

LogicMethod类

package util;

 

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

 

import logic.LogicMethod;

 

public class LogHander implements InvocationHandler{

   private Objecttarget;//被代理的对象

   //通过构造函数设置被代理对象

   public LogHander(Object target){

      this.target=target;

   }

  

   //自定义在Hanlder内部就切入的方法

   public void beforeMethod(Method m) {

      

       System.out.println(target.getClass().getName()+" "+m.getName()+" start");

}

  

   /**

    * invoke方法再反射时调用

    */

   @Override

   public Object invoke(Object proxy, Method method, Object[]args)

        throws Throwable {

      LogicMethod.Begin();

      beforeMethod(method);

      //反射调用目标对象的方法

      method.invoke(target, args);

      LogicMethod.end();

      return null;

   }

 

}

 

LogHander类

package util;

 

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

 

import logic.LogicMethod;

 

public class LogHander implements InvocationHandler{

   private Objecttarget;//被代理的对象

   //通过构造函数设置被代理对象

   public LogHander(Object target){

      this.target=target;

   }

  

   //自定义在Hanlder内部就切入的方法

   public void beforeMethod(Method m) {

      

       System.out.println(target.getClass().getName()+" "+m.getName()+" start");

}

  

   /**

    * invoke方法再反射时调用

    */

   @Override

   public Object invoke(Object proxy, Method method, Object[]args)

        throws Throwable {

      LogicMethod.Begin();

      beforeMethod(method);

      //反射调用目标对象的方法

      method.invoke(target, args);

      LogicMethod.end();

      return null;

   }

 

}

 

测试类TestProxy

import java.lang.reflect.Proxy;

 

import model.User;

 

import util.LogHander;

import DaoImpl.UserDaoImpl;

import dao.UserDao;

 

 

public class TestProxy {

 

   public static void main(String[] args) {

      // TODO Auto-generatedmethod stub

        UserDao userDao=new UserDaoImpl();

      //创建一个Handerler对象并将Handler对象和被代理对象关联

        LogHander logHander=newLogHander(userDao);

        /*newProxyInstance参数含义

         * 第一个参数:代理的类加载器,必须和被代理的对象是一个类加载器

         * 第二个参数含义:代理对象要实现的那些接口

         * 第三个参数:指派方法调用的调用处理程序

         * */

        //编织了目标业务类逻辑和性能监视横切逻辑的handler创建代理类

        UserDao userDaoProxy=(UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(), logHander);

        userDaoProxy.save(new User());

        userDaoProxy.delete(new User());

        }

 

}

运行结果:

自己的逻辑方法

DaoImpl.UserDaoImpl save start

保存用户

结束自己的的逻辑方法

自己的逻辑方法

DaoImpl.UserDaoImpl deletestart

删除用户

结束自己的的逻辑方法

 

详细解说:

1.    Proxy即动态代理类;

2.   Static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用;

它有三个参数:

ClassLoaderloader   ----指定被代理对象的类加载器

Class[]Interfaces   ----指定被代理对象所以事项的接口

InvocationHandlerh ----指定需要调用的InvocationHandler对象

3.invocationHandler解决必须重写invoke方法

invoke方法就是切入逻辑的地方,可以在里面加入个人的逻辑方法

JDK中具体的动态代理类是怎么产生的呢?

1.产生代理类$Proxy0

执行了Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)

将产生$Proxy0类,它继承Proxy对象,并根据第二个参数,实现了被代理类的所有接口,自然就可以生成接口要实现的所有方法了(这时候会重写hashcodetoStringequals三个方法),但是还没有具体的实现体;

2.   将代理类$Proxy0类加载到JVM

这时候是根据Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)它的第一个参数----就是被代理类的类加载器,把当前的代理类加载到JVM

3.   创建代理类$Proxy0类的对象

调用的$Proxy0类的$Proxy0InvocationHandler)构造函数,生成$Proxy0类的对象

参数就是Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)它的第三个参数就是我们自己实现的InvocationHandler对象,我们知道InvocationHandler对象中组合加入了代理类代理的接口类的实现类;所以,$Proxy0对象调用所有要实现的接口的方法,都会调用InvocationHandler对象的invoke()方法实现;

4.   生成代理类的class byte

动态代理生成的都是二进制class字节码

时序图:

 

代码优化:

 

在不影响外部逻辑和不对现有的代码做任何改动的前提下,代理模式是一个不错的选择。但是如果有多个类似的接口,面对每个接口都要实现一个类似的Proxy,实在是一个烦琐无味的苦力过程。SpringAOP是如何实现面对这么多的代理的呢?

 

LogHander类改装成

package util;

 

importjava.lang.reflect.InvocationHandler;

importjava.lang.reflect.Method;

importjava.lang.reflect.Proxy;

import java.util.List;

 

import logic.LogicMethod;

 

public class AOPHandlerimplements InvocationHandler{

         private Object target;//被代理的对象

         //该方法返回动态代理对象 与之前的写法一样,只是将代码实现搬到这里来了

         public Object bind(Object obj){

                   this.target=obj;

                   returnProxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),this);

         }

   //自定义在Hanlder内部就切入的方法

   public void beforeMethod(Method m) {

      

      System.out.println(target.getClass().getName()+" "+m.getName()+ " start");

}

  

   /**

    * invoke方法再反射时调用

    */

         @Override

         public Object invoke(Object proxy, Method method, Object[]args)

                            throws Throwable {

                   Object result=null;

                   if (method.getName().startsWith("save")){

                            LogicMethod.Begin();

                            beforeMethod(method);

                            //反射调用目标对象的方法

                            result=method.invoke(target, args);

                            LogicMethod.end();

                   }

                   return result;

         }

 

}

 

测试类就可以这样写

 

import model.User;

import util.AOPHandler;

importDaoImpl.UserDaoImpl;

import dao.UserDao;

 

/**

 * Spring AOP 实现

 * @author Administrator

 *

 */

public class TestProxy {

 

         public static void main(String[] args) {

                   // TODO Auto-generated method stub

        UserDao userDao=new UserDaoImpl();

      //创建一个Handerler对象并将Handler对象和被代理对象关联

        AOPHandler aopHandler=new AOPHandler();

        UserDao handler=(UserDao) aopHandler.bind(userDao);

        handler.save(new User());

        handler.delete(new User());

        }

 

}

运行结果:

自己的逻辑方法
DaoImpl.UserDaoImpl save start
保存用户
结束自己的的逻辑方法


对所有的类都适用。解决了用静态Proxy类实现所产生的弊端。

弄懂了原理的东西,现在可以开始如何使用了。SPring 的AoP实现其实比较简单,但是,重要的是还是理解了思想。

AOP术语:

•   切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象.切面可以使用基于 XML Schema的风格或者以 @Aspect注解 @AspectJ风格)来实现.

                           

•   连接点(Joinpoint):

•   通知(Advice):                   

•   切入点(Pointcut):

•   引入(Introduction):                  

•   目标对象(Target Object):

•  AOP代理(AOP Proxy):

通知类型:

Before通知,After通知,After returning通知,Throws通知,Around通知

通知类型介绍:

—  Around通知:包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用前后完成自定义的行为,它们也会选择继续执行连接点或返回它们自己的返回值或抛出异常来结束执行。使用 @Around 注解来声明

 

—  Before通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。使用@Before 注解来声明

 

—  After returning通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。使用 @AfterReturning 注解来声明

 

—  After 通知:在连接点退出后执行的通知,不论正常返回还是抛出异常退出。使用 @After 注解来声明

 

—  Throws通知:在方法抛出异常时执行的通知。使用 @AfterThrowing 注解来声明

了解了概念之后就可以直接上例子了,使用AOP其实不是太大问题!

 

 

package com.xiehande.service;

 

public interface StudentService {

  public void save();

  public void delete();

  public void update();

  public void get();

}

 

 

 

package com.xiehande.service.impl;

 

import com.xiehande.service.StudentService;

 

public class StudentServiceImpl implements StudentService{

 

   @Override

   public void save() {

      // TODO Auto-generatedmethod stub

      System.out.println("调用持久层保存学生信息");

   }

 

   @Override

   public void delete() {

      // TODO Auto-generatedmethod stub

      System.out.println("调用持久层删除学生信息");

   }

 

   @Override

   public void update() {

      // TODO Auto-generatedmethod stub

      System.out.println("调用持久层更新学生信息");

   }

 

   @Override

   public void get() {

      // TODO Auto-generatedmethod stub

      System.out.println("调用持久层获得学生信息");

   }

 

}

 

 

 

package com.xiehande.aop;

 

import java.util.Date;

import java.util.concurrent.ExecutionException;

 

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.AfterThrowing;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.stereotype.Component;

 

@Component(value = "logInterceptor")

@Aspect

public class LogInterceptor{

   /**

    * 声明一个切入点

    */

   @Pointcut("execution(*com.xiehande.service..*.*(..))")

   public void anyMethod() {

 

   };

 

   @Before(value = "anyMethod()")

   public void before() {

      System.out.println("前置通知");

   }

 

   /*

    * 传参的前置通知

    *

    *@Before(value="anyMethod()&args(name)") public voidbeforeParam(String

    * name){System.out.println("前置通知"+name); }

    */

 

   /*

    * @AfterReturning(pointcut="anyMethod()")public void afterReturn(Object

    * result){System.out.println("后置通知"+result); }

    */

 

   @After(value = "anyMethod()")

   public void after() {

      System.out.println("最后后置通知,开发人员的操作被记录下来" + "某某在" +new Date() + "操作了类");

   }

 

   /*

    * @AfterThrowing(pointcut="anyMethod()",throwing="e")public void

    * afterThrows(Exception e){System.out.println("异常通知:"+e.getMessage()); }

    */

   @Around("anyMethod()")

   public Object doBasicProfiling(ProceedingJoinPoint pjp)throws Throwable {

      System.out.println("环绕通知开始");

      Object object = pjp.proceed();

      System.out.println("环绕通知结束");

      return object;

   }

 

}

 

 

package test;

 

import org.junit.Test;

importorg.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

import com.xiehande.service.StudentService;

 

 

public class StudentTest {

 @Test

 public void test(){

           ApplicationContext ctx=newClassPathXmlApplicationContext("beans.xml");

           StudentService ss=(StudentService)ctx.getBean("studentService");

           ss.save();

           ss.delete();

           ss.update();

           ss.get();

  }

}

 

 

(AOP可以用注解和可以使用XML,建议用注解,但是,使用注解也要配置开始注解功能)

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"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-2.5.xsd

           http://www.springframework.org/schema/context

           http://www.springframework.org/schema/context/spring-context-2.5.xsd

           http://www.springframework.org/schema/aop

            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

   <!-- 配置实用annotation注解 -->

   <context:annotation-config/>

   <!-- 配置扫描 -->

   <!-- 开启自动扫描功能,如果类包含 @Component, @Repository, @Service, and @Controller,@Required,

      @Autowired, @PostConstruct, @PreDestroy, @Resource,@PersistenceContext and

      @PersistenceUnit这些注解,将会被spring容易管理,并且提供相应的注入 -->

   <context:component-scanbase-package="com.xiehande"></context:component-scan>

   <!-- 配置实用Spring AOP-->

   <aop:aspectj-autoproxyproxy-target-class="false"/>

   <!--

   <bean id="logInterceptor"class="com.xiehande.aop.LogInterceptor"></bean>

    -->

    <beanid="studentService"class="com.xiehande.service.impl.StudentServiceImpl"></bean>

   <!--<aop:config>

      <aop:pointcut expression="execution(public *com.xiehande.dao.impl..*.*(..))"

        id="userDAOImplPointCut" />

      <aop:aspect id="logAspect" ref="logInterceptor">

         <aop:pointcutexpression="execution(public * com.xiehande.dao.impl..*.*(..))"

           id="userDAOImplPointCut"/>这个也是可以写到里面的,只是pointcut的范围只能在这个aspect里面

        <aop:before method="before" pointcut-ref="userDAOImplPointCut"/>

         <aop:beforemethod="before" pointcut-ref="public *com.xiehande.dao.impl..*.*(..)"/>

      </aop:aspect>

   </aop:config>

  

--></beans>

 

 

导包

运行结果:

log4j:WARN No appenders could befound for logger(org.springframework.context.support.ClassPathXmlApplicationContext).

log4j:WARN Please initialize thelog4j system properly.

前置通知

环绕通知 开始

调用持久层保存学生信息

最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类

环绕通知 结束

前置通知

环绕通知 开始

调用持久层删除学生信息

最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类

环绕通知 结束

前置通知

环绕通知 开始

调用持久层更新学生信息

最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类

环绕通知 结束

前置通知

环绕通知 开始

调用持久层获得学生信息

最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类

环绕通知 结束

 

 

说明

1.pointcut语法

      execution ——for matching method executionjoin points

      within —— 指定连接点所在的Java类型。

      this ——bean reference (Spring AOP proxy)is an instance of the given type

      target —— the target object (application objectbeing proxied) is an instance of the given type

      args —— 指定传入到连接点的参数

      @target —— the class of the executingobject has an annotation of the given type

      @args —— the runtime type of the actualarguments passed have annotations of the given type(s)

      @within —— limits matching to join pointswithin types that have the given annotation

      @annotation —— the subject of the joinpoint (method being executed in Spring AOP) has the given annotation

组合pointcut表达式

      可以使用 &&|| ! 组合pointcut表达式。

execution格式

execution(

      可见性(可选)   ——使用publicprotectedprivate指定可见性。也可以为*,表示任意可见性

      返回值类型(必须) ——指定返回值类型

      声明类型(可选)——  java

      方法名(参数模式)(必须) ——方法名称,和方法接收的参数 

      异常模式(可选) ——指定方法签名中是否存在异常类型

      )

pointcut表达式中可以使用 *.. +等通配符。

      * :表示若干字符(排除 .在外)

      .. :表示若干字符(包括 .在内)

      + :表示子类,比如Info+表示Info类及其子类

2.定义Advice

       advice在关联的pointcut表达式的方法运行前(before)、运行后(after)、运行前后(around)执行。

Before Advice:

       使用@Before定义Before Advice

       参数:value :绑定到AdvicePointcut表达式

After returningadvice

       在匹配的方法执行之后运行该Adivce。使用@AfterReturning进行注释。

       参数:valuepointcut:绑定到AdvicePointcut表达式

              returningString类型,将方法的返回值绑定到Advice的参数名上。

After throwingadvice

       pointcut匹配的方法抛出异常后,执行pointcut关联的Advice。使用@AfterThrowing进行注解。

       参数:valuepointcut:绑定到AdvicePointcut表达式

              throwingString类型,将抛出的异常绑定到Advice的参数上。

After advice

       使用After进行注解。

       参数:value :绑定到AdvicePointcut表达式   

3.参数

 使用JoinPoint

       可以在Advice中的第一个参数中定义org.aspectj.lang.JoinPoint类型的参数(在around Advice中使用ProceedingJoinPoint类型的参数)

        java.lang.Object[]       getArgs()  —— returns the methodarguments

        Signature     getSignature()  ——  returns a description of themethod that is being advised

        java.lang.Object  getTarget()  —— returns the targetobject 

        java.lang.Object  getThis()  —— returns the proxy object

pointcut中使用args传递参数