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

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

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

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

 @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包依赖:

  <!-- spring aop -->
       <dependency>
		 <groupId>org.aspectj</groupId>
		 <artifactId>aspectjweaver</artifactId>
		 <version>1.6.8</version>
		</dependency>
    <dependency>

applicationContext.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: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>

切面工具类:

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实体类:

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接口类:

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接口实现类:

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测试类:

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();
		}
	}

}

允许测试类的结果:

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” .

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

  <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的原理和各种术语。

先上图:

spring 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

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();
    }

}

测试类 

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();
		
	}

}

结果:

aaaaa
get person
bbbbb

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

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

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

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

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

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

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

 

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

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

 

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

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 奔图打印机官网驱动_施乐105P一样的打印机

    奔图打印机官网驱动_施乐105P一样的打印机奔图P3060DW打印机驱动带给大家官方最新驱动程序,这款打印机十分小巧功能却很全面,高速双面黑白激光打印机可以满足大家日常的工作及其它需求,驱动程序非常必要,成功安装后方可使用打印机。奔图P3060DW打印机参数:型号P3060DW打印参数打印速度30ppm(A4)32ppm(Letter)首页打印时间≤8.5秒最大月打印量25000页建议月打印量250页到3000页分辨率(dpi)最大12…

  • Pycharm安装django包[通俗易懂]

    Pycharm安装django包[通俗易懂]**pycahrm安装django1.点击file,找到settings点击**2.找到project,点击projectinterpret3.点击旁边的加号4.搜索栏输入django搜索,看到django选项后直接点击然后安装最后安装成功后会有一个successful的提示,然后安装完成后可以在电脑里面打开cmd命令行激活django看是否能运行:5.输入activatedjango激活到此,django安装包已经安装成功…

  • git强制删除本地分支_Git分支

    git强制删除本地分支_Git分支删除分支命令删除一条分支:gitbranch-DbranchName删除当前分支外的所有分支:gitbranch|xargsgitbranch-d删除分支名包含指定字符的分支:gitbranch|grep‘dev*’|xargsgitbranch-d命令解释|道命令,用于将一串命令串联起来。前面命令的输出可以作为后面命令的输入。gitbranch用于列出本地所有分支。xargsxargs是给命令传递参数.

    2022年10月16日
  • accessors 作用_EasyExcel与@Accessors(chain = true)不兼容分析

    accessors 作用_EasyExcel与@Accessors(chain = true)不兼容分析EasyExcelEasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel.github地址:https://github.com/alibaba/easyexcelAccessors@Accessors注解用来配置lombok如何产生和显示getters和setters的方法。public@interfaceAcce…

    2022年10月31日
  • LAMP配置-php.ini

    LAMP配置-php.ini[PHP]zlib.output_compression=Offimplicit_flush=Offunserialize_callback_func=serialize_precision=-1disable_functions=disable_classes=zend.enable_gc=Onexpose_php=Onmax_execution_time=30max_input_time=60memory_limit=128.

  • 网站备案是域名备案还是服务器备案(域名备案一定要服务器吗)

    网站域名服务器怎么备案内容精选换一换WordPress简称WP,最初是一款博客系统,后逐步演化成一款免费的CMS(内容管理系统/建站系统)。本文档指导用户使用华为云市场镜像“Wordpress官方正式版”部署WordPress博客系统。已购买虚拟私有云和弹性公网IP。如果规划为网站配置域名,需已经购买好相应的域名。弹性云服务器所在安全组添加了如表1所示的安全组规则,具体步骤空壳网站指备案主体已在…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号