AOP实现的解决流程

来源:互联网 发布:广州淘宝公司招聘 编辑:程序博客网 时间:2024/05/29 18:10

(一)AOP:Aspect Oriented Programming(面向切面的编程),在程序方法的前后加自己的逻辑。

 

1、问题 


问题:想要添加日志记录、性能监控、安全监测 

 

2、最初解决方案 

2.1、最初解决方案

缺点:太多重复代码,且紧耦合

 

2.2、抽象类进行共性设计,子类进行个性设计,此处不讲解,缺点一荣俱荣,一损俱损

 

2.3、使用装饰器模式/代理模式改进的解决方案

装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说, 装饰器模式相比生成子类更为灵活。
代理模式:为其他对象提供一种代理以控制对这个对象的访问。


 
缺点:紧耦合,每个业务逻辑需要一个装饰器实现或代理
 
2.4、JDK动态代理解决方案(通过反射机制调用被代理类的方法
大体流程:一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。 

 JDK

public interface UserDao {    public void add();    public void update();}
复制代码
public class UserDaoImpl implements UserDao {    public void add() {        System.out.println("添加用户...");    }    public void update() {        System.out.println("修改用户...");    }}
复制代码
复制代码
public class JDKProxy implements InvocationHandler{    private UserDao userDao;    public JDKProxy(UserDao userDao) {        super();        this.userDao = userDao;    }    public UserDao createProxy() {        UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);        return proxy;    }    // 调用目标对象的任何一个方法 都相当于调用invoke();    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if("add".equals(method.getName())){            // 记录日志:            System.out.println("日志记录=================");            Object result = method.invoke(userDao, args);            return result;        }        return method.invoke(userDao, args);    }}
复制代码
复制代码
@Testpublic void demo2(){  // 被代理对象   UserDao userDao = new UserDaoImpl();   // 创建代理对象的时候传入被代理对象.   UserDao proxy = new JDKProxy(userDao).createProxy();   proxy.add();   proxy.update();   /**    * 输出:    *     日志记录=================    *     添加用户...    *     修改用户...    */    }

缺点:使用麻烦,不能代理类,只能代理接口  

 

CGLIB动态代理解决方案(通过类似索引的方式直接调用被代理类的方法
大体流程:就是通过继承。因为如果Proxy 继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。
CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

 Hibernate支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成(Hibernate实际使用Javassist生成代理,该代理对象就是持久化类的子类的实

例),现在做cglib的开发,可以不用直接引入cglib的包.因为spring核心中已经集成了cglib。

CGLIB生成代理机制:其实生成了一个真实对象的子类

这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。

public class ProductDao {        public void add(){        System.out.println("添加商品...");    }    public void update(){        System.out.println("修改商品...");    }}

该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。
public class CGLibProxy implements MethodInterceptor{    private ProductDao productDao;    public CGLibProxy(ProductDao productDao) {        super();        this.productDao = productDao;    }      public ProductDao createProxy(){        // 使用CGLIB生成代理:        // 1.创建核心类:        Enhancer enhancer = new Enhancer();        // 2.为其设置父类:        enhancer.setSuperclass(productDao.getClass());        // 3.设置回调:        enhancer.setCallback(this);        // 4.创建代理:        return (ProductDao) enhancer.create();    }      public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {        if("add".equals(method.getName())){            System.out.println("日志记录==============");            Object obj = methodProxy.invokeSuper(proxy, args);            return obj;        }        return methodProxy.invokeSuper(proxy, args);    }}
public void demo2(){  ProductDao productDao = new ProductDao();  ProductDao proxy = new CGLibProxy(productDao).createProxy();  proxy.add();  proxy.update();  /**  * 输出:  *     日志记录=================  *     添加用户...  *     修改用户...  */}

结论:Spring框架,如果类实现了接口,就使用JDK的动态代理生成代理对象,如果这个类没有实现任何接口,使用CGLIB生成代理对象

   CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。(final修饰的类不能被继承)


优点:能代理接口和类
缺点:使用麻烦,不能代理final类

 

动态代理本质 

本质:对目标对象增强
           最终表现为类(动态创建子类),看手工生成(子类)还是自动生成(子类)
代理限制:
           只能在父类方法被调用之前或之后进行增强(功能的修改),不能在中间进行修改,要想在方法调用中增强,需要ASM(java 字节码生成库)
其他动态代理框架
jboss:javassist (hibernate 3.3中默认为javassist)
                           (hibernate 3.3之前中默认为cglib)

 
 
2.5、AOP解决方案(通用且简单的解决方案)
Java代码  收藏代码
  1. @Aspect  
  2. public class PayEbiAspect {      
  3.     @Pointcut(value="execution(* pay(..))")  
  4.     public void pointcut() {}  
  5.     @Around(value="pointcut()")  
  6.     public Object around(ProceedingJoinPoint pjp) throws Throwable {  
  7.         //1.记录日志  
  8.         //2.时间统计开始  
  9.         //3.安全检查  
  10.         Object retVal = pjp.proceed();//调用目标对象的真正方法  
  11.         //4.时间统计结束  
  12.         return retVal;  
  13.     }  
  14. }  
编程模型
Java代码  收藏代码
  1. //2 切入点  
  2. @Pointcut(value="execution(* *(..))")  
  3. public void pointcut() {}  
  4. //3 拦截器的interceptor  
  5. @Around(value="pointcut()")  
  6. public Object around(ProceedingJoinPoint pjp) throws Throwable {  
  7.     Object retVal=null;  
  8.     //预处理  
  9.     //前置条件判断  
  10.     boolean ok = true;  
  11.     if(!ok) {//不满足条件  
  12.        throw new RuntimeException("你没有权限");  
  13.     }  
  14.     else {//调用目标对象的某个方法  
  15.          retVal = pjp.proceed();   
  16.     }  
  17.     //后处理  
  18.     return retVal;  
  19. }  
 
缺点:依赖AOP框架 
   
横切关注点的表现有:  
  ·代码纠结/混乱——
当一个模块或代码段同时管理多个关注点时发生这种情况。如我既要实现业务、还要实现安全和事务。即有些关注点同时被多个不同的模块实现。实现了重复的功能。
  ·代码分散——当一个关注点分布在许多模块中并且未能很好地局部化和模块化时发生这种情况 。如许多模块调用用户是否登录验证代码。调用了重复的功能。
 
 ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为。
ASM是一个轻量级的类库,但需要涉及到JVM的操作和指令;相比而言,Javassist要简单的多,完全是基于Java的API,但其性能相比前二者要差一些。
 

1.      Javassist不支持要创建或注入的类中存在泛型参数

2.      Javassist对@类型的注解(Annotation)只支持查询,不支持添加或修改


Java 源文件经过 javac 编译器编译之后,将会生成对应的二进制文件。每个合法的 Java 字节码文件都具备精确的定义,而正是这种精确的定义,才使得 Java 虚拟机得以正确读取和解释所有的 Java 字节码文件。

Java 字节码文件是 8 位字节的二进制流。数据项按顺序存储在 class 文件中,相邻的项之间没有间隔,这使得 class 文件变得紧凑,减少存储空间。