欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

浅谈AOP的理解以及实现方法

程序员文章站 2022-06-05 13:42:37
...

在之前有一次跟一个大牛的讨论的时候,发现自己对AOP方面的知识理解还不够透彻,因此,在最近一段时间的学习中,重新梳理了一下AOP方面的相关知识

什么是AOP

AOP就是指面向切面编程,它主要是在业务实现中将一些业务逻辑需要的公有的功能给抽取出来,那么在需要使用到这些功能的时候,只需要将其切入,不用每个业务都编码一次 。这种技术也可以把业务中分散的公共代码块集中起来。
对于代码升级和维护很有利,如果要修改公共的这部分,那么只需要在抽出的这段代码中修改,不需要修改其他地方。

如何实现AOP功能

AOP的实现方法有两种:
**JDK动态代理:**通过代理模式来实现AOP,普通的代理实现,如果要对被代理类中的某些方法增强,就要把这些方法都重写一遍,即使是做的相同操作。
JDK动态代理可以解决这个问题,以打印日志为例,如果我想在方法执行的前后打印日志,不用在每个需要打印的方法中都加上打印日志操作,只要知道需要打印日志的方法和形参,就自动执行打印日志以及方法操作。
动态代理步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法(会自动用invoke()进行处理)
其中关于动态代理的相关信息,在这篇博客中有详细介绍
https://blog.csdn.net/jiankunking/article/details/52143504
JDK动态代理的实现方式

public interface IDemo {//接口
      
      public void say();
      public void hello();    
}

public class Demo implements IDemo {
      @Override
      public void say() {
            // TODO Auto-generated method stub
            System.out.println("AOP JDK demo");
      }
      @Override
      public void hello() {
            // TODO Auto-generated method stub
            System.out.println("AOP JDK method hello");
      }
}
//实现了InvocationHandler 
// DynDemo 是一个代理类
public class DynDemo implements InvocationHandler 
{ 
      // 此时OBJ是要被代理的对象
    private Object obj;
    //这是动态代理的好处,被封装的对象是Object类型,接受任意类型的对象 
 
    public DynDemo() 
    { 
    } 
 
    // 此时参数obj是要被代理的对象
    public DynDemo(Object obj) 
    { 
        this.obj = obj; 
    } 
 
    //这个方法不是我们显示的去调用 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
    { 
      System.out.println(method.getName());
      if(method.getName().equals("hello"))//还可以控制哪些方法需要切入,不过这里是硬编码,可以通过配置实现
      {
             method.invoke(obj, args); 
             return null;
      }
      
        System.out.println("before calling " + method); 
 
        method.invoke(obj, args);  // 执行被代理对象的切入点
 
        System.out.println("after calling " + method); 
 
        return null; 
    } 
 
} 

//客户端:生成代理实例,并调用了say()方法 
public class TestDemo { 
 
    public static void main(String[] args) throws Throwable{ 
        // TODO Auto-generated method stub 
 
        IDemo rs=new Demo();//这里指定被代理类 
        InvocationHandler ds=new DynDemo(rs); 
        Class<?> cls=rs.getClass(); 
         
        //以下是一次性生成代理 
         
         IDemo subject=(IDemo) Proxy.newProxyInstance( 
                cls.getClassLoader(),cls.getInterfaces(), ds); 
        
         
        //这里可以通过运行结果证明subject是Proxy的一个实例,这个实例实现了IDemo接口 
        System.out.println(subject instanceof Proxy); 
         
        //这里可以看出subject的Class类是$Proxy0,这个$Proxy0类继承了Proxy,实现了IDemo接口 
        System.out.println("subject的Class类是:"+subject.getClass().toString()); 
         
        System.out.print("subject中的属性有:"); 
         
        Field[] field=subject.getClass().getDeclaredFields(); 
        for(Field f:field){ 
            System.out.print(f.getName()+", "+f.getType()+" # ");
            f.setAccessible(true);
            System.out.println(f.get(subject));
        } 
         
        System.out.print("\n"+"subject中的方法有:"); 
         
        Method[] method=subject.getClass().getDeclaredMethods(); 
         
        for(Method m:method){ 
            System.out.print(m.getName()+", "); 
        } 
         
        System.out.println("\n"+"subject的父类是:"+subject.getClass().getSuperclass()); 
         
        System.out.print("\n"+"subject实现的接口是:"); 
         
        Class<?>[] interfaces=subject.getClass().getInterfaces(); 
         
        for(Class<?> i:interfaces){ 
            System.out.print(i.getName()+", "); 
        } 
 
        System.out.println("\n\n"+"运行结果为:");
        //subject是自动生成的实际代理类的实例,在执行背带里类中的方法时,会自动调用invoke()处理,
        //invoke的形参:proxy就是这里的subject,方法就是调用的方法比如say,hello;参数就是调用方法中的形参
        subject.say(); 
        subject.hello();
    } 
} 

JDK动态代理解决了重复切入代码的问题,但是也要求被代理类和代理类都实现相同的接口,依赖于接口来完成,字节码增强技术则不用

**字节码增强技术:**这种方式实现AOP,通过对父类的继承,父类的每个可以继承的方法都是连接点(可以切入的点),子类对父类需要增强的方法重写,在调用父类该方法的前后可以增加其它操作

SpringAOP
在上面的代码实现中,invoke方法中在方法前后切入的代码仍然是写死的,并且对于不同的功能切入还要自己写不同的代理类DynDemo(实现InvocationHandler 接口的类) ,
而在springAOP中将这两部分通过配置文件来配置完成,不需要程序员自己实现,就可以大大增强其灵活性,修改和维护也会更方便

<!--配置文件beans.xml-->
<?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:context="http://www.springframework.org/schema/context"
      xmlns:mvc="http://www.springframework.org/schema/mvc"
      xsi:schemaLocation="http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd">
     <!-- 被代理的类或者说需要被切入的类 -->
    <bean id="demoService" class="com.hong.springaop.ServiceDemo">
        <property name="name" value="hugeyurt" />
        <property name="url" value="hugeyurt.com" />
    </bean>
   
    <!-- 配置方法前后的切入代码,即增加的操作 -->
   <bean id="beforeDemo" class="com.hong.springaop.DemoBeforeAdvice" />
    <bean id="afterDemo"  class="com.hong.springaop.AfterAdviceDemo"/>
    <bean id="beforeDemo2" class="com.hong.springaop.BeforeDemoTwo"/>
   
    <!-- 配置代理工厂,负责创建代理类,如果没有使用接口代理就生成  被切入类(这里是ServiceDemo)的子类 -->
    <bean id="ServiceProxy"
                 class="org.springframework.aop.framework.ProxyFactoryBean">
         <!-- target是被切入类的实例对象 -->
        <property name="target" ref="demoService" />
       
        <!-- 这里配置切入操作,如果没有配置,那么方法就不会被拦截 。都是方法前或都是方法后就按照配置顺序执行-->
        <property name="interceptorNames">
            <list>
                <value>beforeDemo2</value>
                 <value>beforeDemo</value>
                <value>afterDemo</value>
               
            </list>
        </property>
    </bean>
</beans>
public class ServiceDemo {//被代理的类
    private String name;
    private String url;
    public void setName(String name) {
        this.name = name;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public  void printName() {
        System.out.println("name : " + this.name);
    }
    public void printURL() {
        System.out.println("company : " + this.url);
    }
    public void printThrowException() {
        throw new IllegalArgumentException();
    }
}

public class BeforeDemoTwo implements MethodBeforeAdvice {//在方法前的操作,实现MethodBeforeAdvice接口
      @Override
      public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
            // TODO Auto-generated method stub
            
            System.out.println("before demo 2.....");
      }
}

//在方法后的操作,实现AfterReturningAdvice接口
public class AfterAdviceDemo implements AfterReturningAdvice
{
      @Override
      public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
            // TODO Auto-generated method stub
            System.out.println("after advice demo.....");
      }
      
}

//测试类
public class App {
    public static void main(String[] args) {
        ApplicationContext appContext = new ClassPathXmlApplicationContext(
                new String[] { "beans.xml" });
    ServiceDemo demo =(ServiceDemo) appContext.getBean("ServiceProxy");
    System.out.println(demo.toString());
 //这里的demo从容器中取出,其实是容器配置的工厂创建出的ServiceDemo的子类,由打印的class可以看出
     System.out.println(demo.getClass());
    System.out.println(demo.getClass().getSuperclass());
        //System.out.println("*************************");
        demo.printName();
       /* System.out.println("*************************");
        demo.printURL();
        System.out.println("*************************");*/
       
    }

上述方式对于配置仍然很麻烦,需要配置被代理的函数和切入操作,以及生成代理类的工厂
因此我们引入了springAOP,在springAOP中我们简化了配置,将生成代理类这一部分也隐藏起来,直接配置一个环绕类和拦截条件来进行实现

<?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:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

<!-- 被代理的类或者说需要被切入的类 -->
 <bean id="userInfo" class="com.hong.springaop.UserInfo"></bean>

<!-- 配置环绕类 -->
 <bean id="roundDemo" class="com.hong.springaop.RoundAdviceDemo"></bean>

   <aop:config>
            <!--切入点 -->
            <aop:pointcut id="methodPoint"
                  expression="execution(* com.hong..*(..)) " />
                  <!--在该切入点使用自定义拦截器 ,advisor:切面-->
            <aop:advisor pointcut-ref="methodPoint" advice-ref="roundDemo" />
      </aop:config>
</beans>

总结

以上就是自己对AOP的简单理解,以及简单的实现分析。

相关标签: AOP面向切面编程