spring——aop


一、AOP介绍

说到aop(Aspect-Oriented Programming,即面向切面编程)就想到了oop(),oop采用继承、组合来实现代码重用,这是一种纵向的方式。而aop是一种横向的机制。aop可以让开发人员把注意力集中到真正所关心的核心业务上去

类和切面的关系:
在这里插入图片描述

1.1 术语

切面、连接点、切入点:
在这里插入图片描述

  1. Aspect (切面):在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、
    曰志等)的,如(图:类和切面的关系)中的 Aspect 该类要被 Spring 容器识别为切面,需要在配置文件中通<bean>元素指定。
  2. Joinpoint (连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例
    如方法的调用或异常的抛出。在Spring AOP 中,连接点就是指方法的调用。
  3. Pointcut (切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点,如(图切面、连接点、切入点)所示 通常在程序中,切入点指的是类或者方法名,如某个通知要应用到所有以 add开头的方法中,那么所有满足这一规则的方法都是切入点。
  4. Advice( 通知/增强处理): AOP 框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码 可以将其理解为切面类中的方法,它是切面的具体实现。
  5. Target Object (目标对象):是指所有被通知的对象,也称为被增强对象。 如果 AOP框架采用的是动态的 AOP 实现,那么该对象就是一个被代理对象。
  6. Proxy (代理):将通知应用到目标对象之后,被动态创建的对象。
  7. Weaving (织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

二、动态代理(两种方式)

2.1 jdk动态代理

在开始之前,需要注意几点

  1. 通过调用Proxy类的newProxyInstance()方法来创建代理对象

目录结构:
在这里插入图片描述

Main.java

public class Main {
    public static void main(String argc[]){
        //创建代理对象
        JdkProxy jdkProxy=new JdkProxy();
        //创建目标对象
        UserDao userDao=new UserDaoImpl();
        //获取代理对象增强后的目标对象
        UserDao userDao1= (UserDao) jdkProxy.createProxy(userDao);
        //执行方法
        userDao1.save();
    }
}

UserDao.java

public interface UserDao {
    void save();
}

UserDaoImpl.java 目标类

public class UserDaoImpl implements UserDao {

    public void save() {
        System.out.println("save save save!!!");
    }
}

MyAspect.java 切面

public class MyAspect {

	//通知 advice
    public void permissionCheck() {
        System.out.println("正在进行权限检查……");
        Random r = new Random();
        boolean result = r.nextBoolean();
        String msg = result ? "权限验证成功" : "无权限";

        System.out.println("result:" + msg);

    }
	
	//通知 advice
    public void log() {
        System.out.println("记录日志……");
    }

}

JdkProxy.java 代理对象

public class JdkProxy implements InvocationHandler {
    //声明目标类借口
    private UserDao userDao;

    //创建代理方法
    public Object createProxy(UserDao userDao) {
        this.userDao = userDao;
        // 1.类加载器
        ClassLoader classLoader = JdkProxy.class.getClassLoader();
        // 被代理对象实现的所有接口
        Class[] clazz = userDao.getClass().getInterfaces();
        // 3. 使用代理类,进行增强 返回的是代理后的对象
        return Proxy.newProxyInstance(classLoader, clazz, this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //声明切面
        MyAspect myAspect=new MyAspect();

        //前增强
        myAspect.permissionCheck();
        //在目标类上调用方法,并传入参数
        Object obj=method.invoke(userDao, args);
        //后增强
        myAspect.log();

        return obj;
    }
}

Main.java 测试

public class Main {
    public static void main(String argc[]){
        //创建代理对象
        JdkProxy jdkProxy=new JdkProxy();
        //创建目标对象
        UserDao userDao=new UserDaoImpl();
        //获取代理对象增强后的目标对象
        UserDao userDao1= (UserDao) jdkProxy.createProxy(userDao);
        //执行方法
        userDao1.save();
    }
}

运行结果:
在这里插入图片描述

2.2 CGLIB代理

JDK的动态代理用起来非常简单,但它是有局限性的,使用动态代理的对象必须实现一个或多个接口。

目录结构:
在这里插入图片描述

UserDao.java 现在的UserDao是一个类而不是接口

public class UserDao {
    public void save() {
        System.out.println("save save save!!!");
    }
}

CgliBProxy.java

public class CgliBProxy implements MethodInterceptor {
        //代理方法
        public Object createProxy(Object target){
                //创建一个动态类对象
                Enhancer enhancer=new Enhancer();
                //确定要增强的类,设置其父类
                enhancer.setSuperclass(target.getClass());
                //添加回调函数
                enhancer.setCallback(this);
                //返回创建的代理类
                return  enhancer.create();
        }


        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //创建切面类对象
                MyAspect myAspect=new MyAspect();
                //前增强
                myAspect.permissionCheck();
                //目标方法执行
                Object obj=methodProxy.invokeSuper(o, objects);
                //后增强
                myAspect.log();
                return obj;
        }
}

Main.java

public class Main {
    public static void main(String argc[]){
        /* jdk动态代理方法
        //创建代理对象
        JdkProxy jdkProxy=new JdkProxy();
        //创建目标对象
        UserDao userDao=new UserDaoImpl();
        //获取代理对象增强后的目标对象
        UserDao userDao1= (UserDao) jdkProxy.createProxy(userDao);
        //执行方法
        userDao1.save();*/

        //CGLIB动态代理方法
        //创建代理对象
        CgliBProxy cgliBProxy=new CgliBProxy();
        //创建目标对象
        com.fengli.CGLlBProxy.UserDao userDao=new com.fengli.CGLlBProxy.UserDao();
        //获取代理对象增强后的目标对象
        com.fengli.CGLlBProxy.UserDao userDao1= (com.fengli.CGLlBProxy.UserDao) cgliBProxy.createProxy(userDao);
        //执行方法
        userDao1.save();
    }
}

运行结果:
在这里插入图片描述


三、基于代理类的AOP实现

3.1 Spring的通知类型

  1. org.springframework.aop.MethodBeforeAdvice(前置通知)
    在目标方法执行前实施增强,可以应用于权限管理等功能。
  2. org.springframework.aop.AfterReturningAdvice(后置通知)
    在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除
    临时文件等功能。
  3. org.aopalliance.intercept.MethodInterceptor(环绕通知)
    在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
  4. org.springframework.aop.ThrowsAdvice(异常抛出通知)
    在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
  5. org.springframework.aop.IntroductionInterceptor(引介通知)
    在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。

3.2 ProxyFactoryBean

使用ProxyFactoryBean是创建aop代理的最基本的方式

3.2.1 例子:

目录结构:
在这里插入图片描述

切面类 MyAspects.java

package com.fengli.proxyFactoryBean;


import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;

/******************************************************************************
 * @Package: [com.fengli.proxyFactoryBean.MyAspects]
 * @ClassName: [MyAspects]
 * @Description: [该类是用来演示使用ProxyFActoryBean创建aop代理]
 * @Author: [LETFL@founder.com.cn]
 * @CreateDate: [2018/12/10 15:42]
 * @UpdateUser: [LETFL@founder.com.cn (如多次修改保留历史记录,增加修改记录)]
 * @UpdateDate: [2018/12/10 15:42,(如多次修改保留历史记录,增加修改记录)]
 * @UpdateRemark: [说明本次修改内容, (如多次修改保留历史记录 , 增加修改记录)]
 * @Version: [v1.0]
 * ${tags}
 */
public class MyAspects implements MethodInterceptor {


    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        check_Permissions();
        //执行目标方法
        Object obj=methodInvocation.proceed();
        log();
        return obj;
    }
    public void check_Permissions(){
        System.out.println("模拟检查权限……");
    }
    public void log(){
        System.out.println("模拟记录日志……");
    }
}

proxyFactoryBeanApplication.xml proxyFactoryBean 的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--1 目标类 -->
    <bean id="userDao" class="com.fengli.JDKDynamic.UserDaoImpl"/>
    <!--2 切面类 -->
    <bean id="myAspect" class="com.fengli.proxyFactoryBean.MyAspects"/>
    <!--3 使用ProxyFactoryBean创建一个代理对象 -->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--3.1 创建代理实现的接口-->
        <!--<property name="proxyInterfaces" value="com.fengli.JDKDynamic.UserDao"/>-->
        <!--3.2 指定目标对象-->
        <property name="target" ref="userDao"/>
        <!--3.3 指定切面,植入环绕通知-->
        <property name="interceptorNames" value="myAspect"/>
        <!--3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理-->
        <property name="proxyTargetClass" value="false"/>
    </bean>
</beans>

测试类 ProxyFactoryBeanTest.java

package com.fengli.proxyFactoryBean;

import com.fengli.JDKDynamic.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/******************************************************************************
 * @Package: [com.fengli.proxyFactoryBean.ProxyFactoryBeanTest]
 * @ClassName: [ProxyFactoryBeanTest]
 * @Description: [一句话描述该类的功能]
 * @Author: [LETFL@founder.com.cn]  
 * @CreateDate: [2018/12/10 16:00]   
 * @UpdateUser: [LETFL@founder.com.cn (如多次修改保留历史记录,增加修改记录)]   
 * @UpdateDate: [2018/12/10 16:00,(如多次修改保留历史记录,增加修改记录)]   
 * @UpdateRemark: [说明本次修改内容, (如多次修改保留历史记录 , 增加修改记录)]
 * @Version: [v1.0]
 * ${tags}
 */
public class ProxyFactoryBeanTest {
    public static void main(String argc[]) {
        String xmlPath = "proxyFactoryBeanApplicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
        //执行方法
        userDao.save();
    }
}

运行结果:
在这里插入图片描述

四、Aspectj开发

4.1 基于xml的aspectj开发

目录结构:
在这里插入图片描述

首先编写切面类:
MyAspect.java

package com.fengli.XMLAspectJ;

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

/******************************************************************************
 * @Package: [com.fengli.XMLAspectJ.MyAspect]
 * @ClassName: [MyAspect]
 * @Description: [切面类,在此类中编写通知]
 * @Author: [LETFL@founder.com.cn]  
 * @CreateDate: [2018/12/10 13:31]   
 * @UpdateUser: [LETFL@founder.com.cn (如多次修改保留历史记录,增加修改记录)]   
 * @UpdateDate: [2018/12/10 13:31,(如多次修改保留历史记录,增加修改记录)]   
 * @UpdateRemark: [说明本次修改内容, (如多次修改保留历史记录 , 增加修改记录)]
 * @Version: [v1.0]
 * ${tags}
 */
public class MyAspect {
    //前置通知
    public void myBefor(JoinPoint joinPoint){
        System.out.print("前置通知:模拟执行权限检查……");
        System.out.print("目标类是:"+joinPoint.getTarget());
        System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
    }

    //后置通知
    public void myAferReturning(JoinPoint joinPoint){
        System.out.print("后置通知:模拟记录日志……");
        System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
        
    }
    
   /** @Title: myAround
   * @Description: 环绕通知,必须是Object类型方返回值,必须接收一个参数,类型为ProceedingJoinPoint,必须throws Throwable
   * @param proceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
   * @return java.lang.Object    返回类型
   * @throws
   */

    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        //开始
        System.out.println("环绕开始:执行目标方法之前,模拟开启事务……");
        //执行当前目标方法
        Object obj=proceedingJoinPoint.proceed();
        //结束
        System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
        return obj;
    }

    /** @Title: myAfterThrowing
    * @Description: 异常通知
    * @param joinPoint
    * @param  e
    * @return void    返回类型
    * @throws
    */

    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("最终通知:"+"出错了"+e.getMessage());
    }

    /** @Title: myAfter
    * @Description: 最终通知
    * @param
    * @return void    返回类型
    * @throws
    */

    public void myAfter(){
        System.out.println("最终通知:模拟方法结束后的释放资源……");
    }

}

编写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: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 http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--1.目标类 -->
    <bean id="userDao" class="com.fengli.JDKDynamic.UserDaoImpl"/>
    <!--2.切面 -->
    <bean id="myAspect" class="com.fengli.XMLAspectJ.MyAspect"/>
    <!--3.aop编程 -->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect ref="myAspect">
            <!-- 3.1配置切入点,通知最后增强哪些方法 -->
            <aop:pointcut id="myPointcut" expression="execution(* com.fengli.JDKDynamic.UserDaoImpl.*(..))"/>
            <!-- 3.2 关联通知Advice和切入点pointCut -->
            <!-- 3.2.1 前置通知-->
            <aop:before method="myBefor" pointcut-ref="myPointcut"/>
            <!--3.2.2 后置通知-->
            <aop:after-returning method="myAferReturning" pointcut-ref="myPointcut" />
            <!--3.2.3 环绕通知-->
            <aop:around method="myAround" pointcut-ref="myPointcut" />
            <!--3.2.4 抛出通知:用于处理程序发生异常-->
            <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
            <!--3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
            <aop:after method="myAfter" pointcut-ref="myPointcut" />
        </aop:aspect>
    </aop:config>
</beans>

测试类 testXMLAspectj.java

package com.fengli.XMLAspectJ;

import com.fengli.JDKDynamic.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/******************************************************************************
 * @Package: [com.fengli.XMLAspectJ.TestXmlAspectj]
 * @ClassName: [com.fengli.XMLAspectJ.TestXmlAspectj]
 * @Description: [一句话描述该类的功能]
 * @Author: [LETFL@founder.com.cn]  
 * @CreateDate: [2018/12/10 14:21]   
 * @UpdateUser: [LETFL@founder.com.cn (如多次修改保留历史记录,增加修改记录)]   
 * @UpdateDate: [2018/12/10 14:21,(如多次修改保留历史记录,增加修改记录)]   
 * @UpdateRemark: [说明本次修改内容, (如多次修改保留历史记录 , 增加修改记录)]
 * @Version: [v1.0]
 * ${tags}
 */
public class TestXmlAspectj {
    public static void main(String argc[]){
        //E:\IdeaProjects\aop\src\main\java\com\fengli\XMLAspectJ\applicationContext.xml
        //String xmlPath="com/fengli/XMLAspectJ/applicationContext.xml";
          String xmlPath= "applicationContext.xml";
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
        //1 从spring获得内容
        UserDao userDao= (UserDao) applicationContext.getBean("userDao");
        //执行方法
        userDao.save();
    }

}

运行结果:
在这里插入图片描述

4.1 基于注解的aspectj开发

目录结构:
在这里插入图片描述

切面类 MyAspect.java

package com.fengli.AnnotationAspectJ;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/******************************************************************************
 * @Package: [com.fengli.XMLAspectJ.MyAspect]
 * @ClassName: [MyAspect]
 * @Description: [基于注解的Aspectj]
 * @Author: [LETFL@founder.com.cn]  
 * @CreateDate: [2018/12/10 13:31]   
 * @UpdateUser: [LETFL@founder.com.cn (如多次修改保留历史记录,增加修改记录)]   
 * @UpdateDate: [2018/12/10 13:31,(如多次修改保留历史记录,增加修改记录)]   
 * @UpdateRemark: [说明本次修改内容, (如多次修改保留历史记录 , 增加修改记录)]
 * @Version: [v1.0]
 * ${tags}
 */
@Aspect
@Component
public class MyAspect {
    //定义切入点表达式
    @Pointcut("execution(* com.fengli.JDKDynamic.UserDaoImpl.*(..))")//对UserDaoImpl的所有方法进行处理
    private void myPointCut(){}

    //前置通知
    @Before("myPointCut()")
    public void myBefor(JoinPoint joinPoint){
        System.out.print("前置通知:模拟执行权限检查……");
        System.out.print("目标类是:"+joinPoint.getTarget());
        System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
    }

    //后置通知
    @AfterReturning(value = "myPointCut()")
    public void myAferReturning(JoinPoint joinPoint){
        System.out.print("后置通知:模拟记录日志……");
        System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
        
    }
    
   /** @Title: myAround
   * @Description: 环绕通知,必须是Object类型方返回值,必须接收一个参数,类型为ProceedingJoinPoint,必须throws Throwable
   * @param proceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
   * @return java.lang.Object    返回类型
   * @throws
   */
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        //开始
        System.out.println("环绕开始:执行目标方法之前,模拟开启事务……");
        //执行当前目标方法
        Object obj=proceedingJoinPoint.proceed();
        //结束
        System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
        return obj;
    }

    /** @Title: myAfterThrowing
    * @Description: 异常通知
    * @param joinPoint
    * @param  e
    * @return void    返回类型
    * @throws
    */
    @AfterThrowing(value = "myPointCut()",throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("最终通知:"+"出错了"+e.getMessage());
    }

    /** @Title: myAfter
    * @Description: 最终通知
    * @param
    * @return void    返回类型
    * @throws
    */
    @After("myPointCut()")
    public void myAfter(){
        System.out.println("最终通知:模拟方法结束后的释放资源……");
    }

}

xml(annotationAspectJ.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: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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
       <!--允许注解,且扫描指定路径的类 -->
        <context:component-scan base-package="com.fengli"/>
        <!--允许切面自动代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

Main.java 测试类

public class Main {
    public static void main(String argc[]){
        String xmlPath= "annotationAspectJ.xml";
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
        //1 从spring获得内容
        UserDao userDao= (UserDao) applicationContext.getBean("userDao");
        //执行方法
        userDao.save();
    }
}

运行结果:
在这里插入图片描述

五、参考资料

Java EE 企业缎应用开发披程( Spring+Spring MVC+MyBatis)
Java EE企业级应用开发教程Spring+Spring MVC+MyBatis》PPT

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值