spring aop面向切面原理,用处和实力讲解

2022-08-09 15:10:29 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

先实例对比说说什么面向切面,看下面代码:

代码语言:javascript复制
 @Override
  public void savePerson() {
        //现在我想把每个保存数据库的语句前后都打印一句话,如下:
        System.out.println("开始保存到数据库.....");
	    save(person);     //请把这句看做是保存数据库的语句
        System.out.println("...保存成功");

  }

上面打印的语句,其实就相当于日志,监控我有没有保存成功,这里我保存的是person对象,如果我还有student,teacher,dog等等很多对象都需要做增删改查操作,是不是在每个增删改查的语句前后都加上这两句话呢?这样不是很繁琐。那么有没有办法让每有执行save操作时就自动前后打印日志呢?这里就应运而生了面向切面AOP

下面再看看面向切面的例子吧!

首先要先搭建一个spring工程,我这里就不做介绍了,朋友们可以参考这个链接的搭建过程https://blog.csdn.net/csdnliuxin123524/article/details/80935836

maven工程加jar包依赖:

代码语言:javascript复制
  <!-- spring aop -->
       <dependency>
		 <groupId>org.aspectj</groupId>
		 <artifactId>aspectjweaver</artifactId>
		 <version>1.6.8</version>
		</dependency>
    <dependency>

applicationContext.xml配置:

代码语言:javascript复制
<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

  <!-- 配置扫描的包 -->
    <context:component-scan base-package="redisCache.service"/>
   <!-- 切面的声明 -->
    <bean id="transaction" class="aop.Transaction"/>
      <!--aop配置  --> 
    <aop:config>
    	 <!-- 切点, 配置aop的切入点id; 是切入点的标识 ;expression 为切入点的表达式 -->
    	 <aop:pointcut expression="execution(* redisCache.service.impl.PersonDaoImpl.*(..))" id="perform"/>
    	 <!-- 切面,配置切面(切面里面配置通知)—— ref 指向声明切面的类 -->
    	 <aop:aspect ref="transaction">
    	 <!-- 	前置通知pointcut-ref 引用一个切入点 -->
         <aop:before method="beginTransaction" pointcut-ref="perform"/>
         <!-- 后置通知   
         <aop:after-returning method="commit" pointcut-ref="perform" returning="val"/> -->
         
    	 </aop:aspect>
    </aop:config>
  
</beans>

切面工具类:

代码语言:javascript复制
package aop;

import java.util.ArrayList;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import redisCache.entity.Person;

/**
 * 切面(spring aop 就不需要拦截器啦)
 * (模拟hibernate里面保存数据要打开事物,然后各种增删改之后,再提交事物。)
 */
public class Transaction {
	public void beginTransaction() {//前置通知
        //打开事物
        System.out.println("begin Transaction");
    }

    /**
     * @param joinPoint 通过joinPoint可以得到目标类和目标方法的一些信息
     * @param val       目标方法的返回值
     *                  和<aop:after-returning returning="val"/>中returning的值保质一致
     */
    public void commit(JoinPoint joinPoint, Object val) {//后置通知
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName);
        System.out.println(joinPoint.getTarget().getClass().getName());
        //提交事物
        System.out.println("commit");
        List<Person> personList = (ArrayList<Person>) val;
        for (Person person : personList) {
            System.out.println(person.getPname());
        }
    }

    public void finalMethod() {
        System.out.println("最终通知");
    }

    public void aroundMethod(ProceedingJoinPoint joinPoint) {//环绕通知
        try {
            System.out.println("around method");
            joinPoint.proceed();//调用目标类的目标方法
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 异常通知
     */
    public void throwingMethod(Throwable except) {
        System.out.println(except.getMessage());
    }
}

person实体类:

代码语言:javascript复制
package redisCache.entity;

public class Person {
	 private Long pid;
	    private String pname;

	    public Long getPid() {
	        return pid;
	    }

	    public void setPid(Long pid) {
	        this.pid = pid;
	    }

	    public String getPname() {
	        return pname;
	    }

	    public void setPname(String pname) {
	        this.pname = pname;
	    }
}

personDao接口类:

代码语言:javascript复制
package redisCache.service;

import java.util.List;

import redisCache.entity.Person;

/**
 * 目标对象和代理对象都实现的接口
 */
public interface PersonDao {
	 	void deletePerson();
	    List<Person> getPerson() throws Exception;
	    void savePerson();
	    void updatePerson();
}

personDaoImpl接口实现类:

代码语言:javascript复制
package redisCache.service.impl;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import redisCache.entity.Person;
import redisCache.service.PersonDao;
/**
 * 目标对象:实现目标接口
 */
@Service("personDao")
public class PersonDaoImpl implements PersonDao{
	 @Override
	    public void deletePerson() {
	        System.out.println("delete perosn");
	    }

	    @Override
	    public List<Person> getPerson() throws Exception {
	        List<Person> personList = new ArrayList<Person>();
	        Person person1 = new Person();
	        person1.setPid(1L);
	        person1.setPname("person1");
	        System.out.println("get person");
	        personList.add(person1);
	        Person person2 = new Person();
	        person2.setPid(2L);
	        person2.setPname("person2");
	        personList.add(person2);
	        return personList;
	    }

	    @Override
	    public void savePerson() {
	        System.out.println("delete perosn");
	    }

	    @Override
	    public void updatePerson() {
	        System.out.println("delete perosn");
	    }
}

TestAop测试类:

代码语言:javascript复制
package redisCache;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import redisCache.service.PersonDao;

public class TestAop {
	
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    	PersonDao personDao=(PersonDao) context.getBean("personDao");
    	try {
			personDao.getPerson();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

允许测试类的结果:

代码语言:javascript复制
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
begin Transaction
get person
begin Transaction
delete perosn

从打印结果中可以看出personDaoImpl实现类的所有方法只要执行就会先打印“begin Transcation” .

上面我们在执行方法前打印的方式称为前置通知,当然在面向切面的术语中还有其他诸如:后置通知,环绕通知,最终通知,异常通知。这里我们都在配置文件中加上,如下:

代码语言:javascript复制
  <aop:config>
    	 <!-- 切点, 配置aop的切入点id; 是切入点的标识 ;expression 为切入点的表达式 -->
    	 <aop:pointcut expression="execution(* redisCache.service.impl.PersonDaoImpl.*(..))" id="perform"/>
    	 <!-- 切面,配置切面(切面里面配置通知)—— ref 指向声明切面的类 -->
    	 <aop:aspect ref="transaction">
    	 <!-- 	前置通知pointcut-ref 引用一个切入点 -->
         <aop:before method="beginTransaction" pointcut-ref="perform"/>
         <!-- 后置通知   
         <aop:after-returning method="commit" pointcut-ref="perform" returning="val"/> -->
         <!--
             	   最终通知
                   *   不能得到目标方法的返回值
                   *   无论目标方法是否有异常,最终通知都将执行
                   *   资源的关闭、连接的释放写在最终通知里
             -->
            <!--<aop:after pointcut-ref="perform" method="finalMethod"/>-->

            <!--
                	    环绕通知
                       *  ProceedingJoinPoint的proceed方法就是目标对象的目标方法
                       *  环绕通知可以控制目标对象目标方法执行
             -->
            <!--
            <aop:around method="aroundMethod" pointcut-ref="perform"/>
             -->
            <!--
               	     异常通知
                      *  在异常通知中获取目标方法抛出的异常
             -->
            <!--<aop:after-throwing method="throwingMethod" pointcut-ref="perform" throwing="except"/>-->
    	 </aop:aspect>
    </aop:config>

切面的优势:

这里百度到一位网友写的不错:https://blog.csdn.net/AlbenXie/article/details/72783393

总结一句话就是:AOP 在不修改源代码的情况下给程序动态统一添加功能。 这样就能够在一个项目及时要在中途需要这么一个功能,那也就只需修改配置文件和加一个类,而没有该已经写好的类的代码。aop明显增加了代码的复用性,也省去了重新测试的时间。


通过实例大概了解aop的用途和优势后我们再结合上面的实例理解aop的原理和各种术语。

先上图:

上面的三条红色的竖向框就是经常说的切面,在这个切面里面有很多的方法,你大可以吧a()看做上面的说道的前置通知,b()看做后置通知,c()看做最终通知等等。总而言之,这些方法都不需要我们去写的,而是aop自动帮我们做好的。我们只要触动了我们的比如“保存方法”就会执行切面里的一系列方法。这样就省去了很多开发时间,也精简了代码。


因为这个AOP–面向切面编程是基于动态代理模式的,所以,要想搞清楚这个AOP,就必须得先了解下,什么是代理模式什么又是动态代理模式动态代理模式的2种实现方式。

在小编的这篇博文中有简单解释代理:https://blog.csdn.net/csdnliuxin123524/article/details/81236007

下面重新写一下动态代理的实例步骤,代码转自一位很强的博主的博文:https://blog.csdn.net/qq_27093465/article/details/53351403

代码语言:javascript复制
package proxy1;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class MyInterceptor implements MethodInterceptor{
	private Object target;//通用目标类
	
	//有参构造器
	public MyInterceptor(Object o) {
		this.target=o;
	}
	@Override
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		if(method.getName().equals("getPerson")){//切入点,因为传入的object类中的方法不是都需要做打印通知的,所以只有满足我们条件的方法才行,这里我以方法名做判断。
            System.out.println("aaaaa");//切面方法a();
            //。。。
            method.invoke(this.target, objects);//调用目标类的目标方法
            //。。。
            System.out.println("bbbbb");//切面方法f();
        }
        return null;
	}
	
	/**
     * 返回代理对象
     * 具体实现,暂时先不追究。
     */
    public Object createProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(this);//回调函数  拦截器
        //设置代理对象的父类,可以看到代理对象是目标对象的子类。所以这个接口类就可以省略了。
        enhancer.setSuperclass(this.target.getClass());
        return enhancer.create();
    }

}

测试类

代码语言:javascript复制
package redisCache;
import redisCache.service.impl.PersonDaoImpl;

public class TestAop2 {
	public static void main(String[] args) throws Exception {
		PersonDaoImpl personDaoImpl=new  PersonDaoImpl();
		MyInterceptor myInterceptor=new MyInterceptor(personDaoImpl);
		PersonDaoImpl personDaoImpl2=(PersonDaoImpl) myInterceptor.createProxy();
		personDaoImpl2.getPerson();
		
	}

}

结果:

代码语言:javascript复制
aaaaa
get person
bbbbb

上面代码,看完之后,就来对应AOP里面的各个概念到实际代码里面去

图上说了5个术语。加上下面的织入,也就是6个啦。

再加上代理对象,这个就比较简单了,测试代码有写注释啦。那么就是一共7个啦。

唯一漏掉的就是“引入”,这个是系统自己实现的,我们就没必要去深究了。

注意理解以下几个概念: 代理对象的方法 = 目标对象的目标方法 所有切面的通知。 织入形成代理对象的方法的过程

通知:实际上就是切面中的方法切入点的理解:只有符合切入点的目标方法,才能加载通知。也就是调用切面的通知(方法)啦,看代码也就是说,切入点是控制代理对象内部的切面方法和目标对象的目标方法是否执行的条件。切面可以不止是一个。每个切面里面的通知即切面方法也是可以有很多的。 连接点的理解:所谓连接点,也就是目标对象或者代理对象之中的方法。为什么说2个都 可以呢?因为如果是jdk实现的动态代理的话,那么目标对象和代理对象要实现共同的接口,如果是cglib实现的动态代理的话,那么代理对象类是目标对象类的子类。都是一个方法啦。所以这么理解就OK的啦。

上面的过程就可以理解为@Transcational注解所起的作用,因为spring事务的管理使用的就是aop动态代理的功能。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/106061.html原文链接:https://javaforall.cn

0 人点赞