Spring学习笔记(四)--AOP面向切面

目录

一、概念

二、原理

三、入门程序

3.1 导入jar包和约束

3.2  写切面类

3.3 配置AOP(横向切入)

3.4 测试

四、AOP术语详解

五、execution表达式

 六、AOP通知示例

6.1 配置版

 6.2 注解版

七、bug修复

7.1 AOP动态代理类型转换出错


一、概念

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


二、原理

OOP:面向对象编程(纵向编程-->继承)

AOP:面向切面编程(横向编程-->代理机制)类似于生活中的插队,可以很轻易的对一个程序的前后进行功能的增强或者取消。

常用于:对程序进行功能增强,权限校验,日志记录,事务控制,性能监控。


三、入门程序

步骤:

  1. 导入jar包
  2. 导入约束
  3. 写增强方法
  4. 横向切入
  5. 测试

需求:使用aop在dao层中的findAll方法前面添加一个权限校验的功能。

3.1 导入jar包和约束

  • pom.xml
<dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
  • application.xml 

xmlns:aop="http://www.springframework.org/schema/aop"                                                          ......                                                                                  http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd

<?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:p="http://www.springframework.org/schema/p"
       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/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

3.2  写切面类

public class MyAspect {
    public void checkPri(){
        System.out.println("此处进行权限校验");
    }
}

3.3 配置AOP(横向切入)

此处需特别注意的是:如果切入点返回类型为List,那么execution返回类型模式为java.util.List

application.xml  

<!--1、配置目标对象-->
    <bean id="productDaoImpl" class="com.jc.dao.impl.ProductDaoImpl"></bean>
    <!--2、配置切面类-->
    <bean id="myAspect" class="com.jc.aspect.MyAspect"></bean>
    <!--3、配置AOP(切面、通知、切入点)-->
    <aop:config>
        <!--3.1 配置切面-->
        <aop:aspect ref="myAspect">
            <!--3.2 配置通知和切入点-->
            <aop:before method="checkPri" pointcut="execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())"></aop:before>
        </aop:aspect>
    </aop:config>

3.4 测试

由图可知,权限校验方法插在了查询的前面执行


四、AOP术语详解

  • Joinpoint:连接点,可以被拦截的点(所有的方法都可以成为连接点)
  • Introduction:引介,类层面的增强(不作为研究)
  • Target:目标,被增强的对象
  • Weaving:织入,将通知(advice)应用到目标(target)的过程
  • Proxy:代理对象,一个类被织入后产生的那个对象
  • Aspect:切面,多个通知和多个切入点的结合
    1. Pointcut:切入点,真正被拦截到的点
    2. Advice:通知/增强,对目标方法进行的操作(权限,日志),方法层面
      1. 前置通知:目标方法之前操作,可以获得切入点的信息,如权限的校验
      2. 后置通知:目标方法之后操作,可以获得目标方法返回值的信息,在方法执行return后执行,抛出异常时不执行,如日志的记录
      3. 环绕通知:目标方法前后都操作,可阻止目标方法的执行,如性能分析,事务操作
      4. 异常抛出通常:程序出现异常的时候,可以获得异常信息,比如事务出异常的时候,得回滚
      5. 最终通知:相当于finally,在方法返回前执行
      6. 引介通知:

五、execution表达式

execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())
[访问控制修饰符] 方法返回值 包名.类名.方法名(参数)
*代表任意
..代表任意参数
+代表当前类及其子类
eg:* * com.jc.dao.*.*(..)      dao包以及其子包下的所有所有方法

 


 六、AOP通知示例

6.1 配置版

1)前置通知、环绕通知、后置通知、异常抛出通知

Dao方法ProductDaoImpl.java

public class ProductDaoImpl implements ProductDao {
    @Override
    public List<Product> findAll() {
        System.out.println("此处为查询数据库操作");
        return null;
    }

    @Override
    public String add() {
        System.out.println("此处为添加数据库操作");
        return "张三";
    }

    @Override
    public String update() {
        int i=1/0;
        System.out.println("dao层更新用户");
        return "小白更新的";
    }

}

AOP配置application.xml

<!--1、配置目标对象-->
    <bean id="productDaoImpl" class="com.jc.dao.impl.ProductDaoImpl"></bean>
    <!--2、配置切面类-->
    <bean id="myAspect" class="com.jc.aspect.MyAspect"></bean>
    <!--3、配置AOP(切面、通知、切入点)-->
    <aop:config>
        <!--3.1 配置切面-->
        <aop:aspect ref="myAspect">
            <!--3.2 配置通知和切入点-->
            <aop:before method="checkPri" pointcut="execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())"></aop:before>
            <aop:after-returning method="writeLog" pointcut="execution(public String com.jc.dao.impl.ProductDaoImpl.add())" returning="result"></aop:after-returning>
            <aop:around method="around" pointcut="execution(public String com.jc.dao.impl.ProductDaoImpl.update())"></aop:around>
            <aop:after-throwing method="error" pointcut="execution(public String com.jc.dao.impl.ProductDaoImpl.update())" throwing="th"></aop:after-throwing>
        </aop:aspect>
    </aop:config>

 切面类MyAspect.java

public class MyAspect {
//    前置通知:权限设置
    public void checkPri(JoinPoint joinPoint){
        System.out.println("此处进行权限校验======"+joinPoint.getSignature().getName());
    }
//    后置通知:日志记录
    public void writeLog(Object result){
        System.out.println("日志记录======"+result);
    }
//    环绕通知:性能监控
    public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
        System.out.println("性能分析1======"+System.currentTimeMillis());
        Object proceed = joinPoint.proceed();//调用目标方法
        System.out.println("性能分析2======"+System.currentTimeMillis());
        System.out.println(proceed);
        return proceed;
    }
//    异常抛出通知
//result就是返回值信息,必须和配置文件中一致
    public void error(Throwable th){
        System.out.println("进行异常通知====>"+th.getMessage());
    }
}

测试类

public class TestApplication {
    @Test
    public void TestAspect(){
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
//        ProductDaoImpl productDao=(ProductDaoImpl)applicationContext.getBean("productDaoImpl");
        ProductDao productDao=(ProductDao)applicationContext.getBean("productDaoImpl");
        productDao.findAll();
        productDao.add();
        productDao.update();
    }
}

测试结果

前置通知、后置通知、环绕通知、异常抛出通知

 

2)最终通知:

 <aop:after method="after" pointcut="execution(public *com.jc.dao.impl.ProductDaoImpl.*(..))"></aop:after>
//    最终通知
    public void after(){
        System.out.println("最终通知======相当于finally");
    }

 6.2 注解版

步骤:

  1. 开启AOP注解支持
  2. 切面类使用注解
  3. 测试

程序代码:

application.xml

<!--1、开启AOP注解支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <!--1、配置目标对象-->
    <bean id="productDaoImpl" class="com.jc.dao.impl.ProductDaoImpl"></bean>
    <!--2、配置切面类-->
    <bean id="myAspect" class="com.jc.aspect.MyAspect"></bean>

MyAspect.java

@Aspect
public class MyAspect {
    /**
     * 切入点注解
     * 如果有多个地方引用了该表达式,可提取一个方法
     * 每次使用时,类名.方法名就可
     */
    @Pointcut(value = "execution(public String com.jc.dao.impl.ProductDaoImpl.update())")
    private void update(){}

//    前置通知:权限设置
    @Before(value = "execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())")
    public void checkPri(JoinPoint joinPoint){
        System.out.println("此处进行权限校验======"+joinPoint.getSignature().getName());
    }
//    后置通知:日志记录
    @AfterReturning(value = "execution(public String com.jc.dao.impl.ProductDaoImpl.add())",returning = "result")
    public void writeLog(Object result){
        System.out.println("日志记录======"+result);
    }
//    环绕通知:性能监控
    @Around(value = "MyAspect.update()")
    public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
        System.out.println("性能分析1======"+System.currentTimeMillis());
        Object proceed = joinPoint.proceed();//调用目标方法
        System.out.println("性能分析2======"+System.currentTimeMillis());
        System.out.println(proceed);
        return proceed;
    }
//    异常抛出通知
//result就是返回值信息,必须和配置文件中一致
    @AfterThrowing(value = "MyAspect.update()",throwing = "th")
    public void error(Throwable th){
        System.out.println("进行异常通知====>"+th.getMessage());
    }
//    最终通知
    @After(value = "execution(public * com.jc.dao.impl.ProductDaoImpl.*(..))")
    public void after(){
        System.out.println("最终通知======相当于finally");
    }
}

 


七、bug修复

7.1 AOP动态代理类型转换出错

问题:

spring aop中的动态代理时,碰到了一个类型转换的问题:

java.lang.ClassCastException: com.sun.proxy.$Proxy5 cannot be cast to com.jc.dao.impl.ProductDaoImpl

原因:

spring使用的动态代理有两种:JDK Proxy 和CGLIB。使用前者必须实现至少一个接口才能实现对方法的拦截。使用后者需要两个jar包:asm.jar和cglib.jar,并修改spring配置文件。当代理对象实现了至少一个接口时,默认使用JDK动态创建代理对象;当代理对象没有实现任何接口时,就会使用CGLIB方法。

如果实现了接口,强制转换必须用父类接口来定义

解决:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值