spring基础AOP入门--day03

4 篇文章 0 订阅

讲到AOP,Aspect Orient Program 面向切面编程。)要先从代理模式入手,毕竟spring AOP的底层是动态代理!
所谓代理:个人理解就像是歌星经纪人,为歌星代理各种功能!没有用到代理之前,要扩展功能还要修改源码,有违开闭原则,而且不安全!所以需要用到代理模式。
实现代理模型的三种方式:
1)静态代理
2)动态代理
​ 2.1) JDK动态代理
​ 2.2 )CGLIB动态代理
###静态代理:

优点:在不修改目标类的情况下对目标的方法进行扩展
缺点:

​1)一个静态代理类只能代理一个目标类
​ 2)静态代理类的每个方法都需要编写重复的代理逻辑,代码比较冗余

要求:

1)和目标(类)实现同样的接口
2)在静态代理类中传入目标对象实例,以便调用目标对象的方法
3)可以在静态代理类的方法中添加代理逻辑代码

public class LogProxy implements UserService{
    //接收目标对象实例
    private UserService userService;

    //使用构造方法传入目标对象实例
    public LogProxy(UserService userService){
        this.userService = userService;
    }
    @Override
    public void save() {
        System.out.println("before=====save");
        
        //调用目标对象的方法
        userService.save();
        System.out.println("after=====save");
    }
JDK动态代理

######优点:
​ 1)一个工具类可以生成任何目标对象的代理对象(更加通用啦)
​ 2)代理逻辑只需要编写一次,就可以应用到所有目标对象的方法上(代码简化)

#####缺点:
​ 目标对象必须有接口,没有接口生成不了JDK动态代理
查阅API可以看出,newpRroxyInstance 方法是用于创建动态代理对象。
static Object newProxyInstance(
​ ClassLoader 类加载器
​ Class[] 目标对象实现的接口列表
​ InvocationHandler 用于编写 代理对象的代理逻辑代码 的接口)
该方法返回的是代理对象!!!

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * 用于生成JDK动态代理对象的工具类
 */
public class LogProxy {
    /**
     * 生成JDK动态代理对象的方法
     *
     * 返回值:生成的JDK动态代理对象
     * 参数:target, 传入目标对象
     */
    public static Object getProxy(Object target){
        /**
         * 参数一:类加载器,JDK动态代理的底层使用类加载器来生成的一个动态类的。通常传入当前类的类加载器即可!!!(LogProxy.class.getClassLoader())
         * 参数二:目标对象的接口列表(所有接口),通常使用目标对象获取接口列表(target.getClass().getInterfaces())
         * 参数三:接口。 该用于编写  代理类的代理逻辑代码。通常我们要提供InvocationHandler接口的实现类(匿名内部类的方式提供)
         */
        return Proxy.newProxyInstance(
                LogProxy.class.getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {

                    /**
                     * invoke方法:用于编写 代理类的代理逻辑代码。
                     *      invoke方法在什么时候会被调用?
                     *            该方法会在调用JDK代理对象的每个方法的时候被执行!!!!!
                     *
                     * @param proxy: 生成JDK动态代理对象
                     * @param method: 目标对象的执行方法的对象
                     * @param args: 目标对象的方法参数列表
                     * @return 返回值:目标对象方法执行后的返回结果
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //获取目标对象的方法名称
                        String methodName = method.getName();

                        System.out.println("before======"+methodName);

                        //获取方法的参数列表
                       /* if(args!=null)
                        System.out.println(Arrays.asList(args));*/

                       //调用目标对象的方法
                        /**
                         * 参数一:执行的对象(必须传入目标对象,不能传入代理对象,否则会死循环)
                         * 参数二:方法的参数列表
                         */
                        Object result = method.invoke(target,args);


                        System.out.println("after======"+methodName);

                        return result;
                    }
                }
        );
    }
}

###CGLIB动态代理
优点: 解决目标对象没有实现接口,就不能使用jdk接口代理的问题
记得cglib依赖(spring-core依赖)
编写生成Cglib代理对象的工具类

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

import java.lang.reflect.Method;

/**
 * 用于生成Cglib代理对象的工具类
 */
public class LogProxy {


    /**
     * 生成Cglib代理对象
     * 返回值:生成的Cglib子类代理对象
     * 参数:目标对象(目标对象没有接口)
     */
    public static Object getProxy(Object target){

        /**
         * 方法返回值:生成的Cglib子类代理对象
         * 参数一:目标对象的类型(target.getClass()) (其实目标对象的类型就是Cglib代理对象 的  父类)
         * 参数二:MethodInterceptor接口,用于编写 代理对象的代理逻辑代码。通常提供MethodInterceptor接口的匿名内部即可
         */
        return Enhancer.create(
                target.getClass(),
                new MethodInterceptor() {
                    /**
                     * intercept方法:在调用代理对象的每个方法的时候会执行
                     * @param proxy: 生成的代理对象
                     * @param method: 目标对象的方法对象
                     * @param args: 目标对象的方法参数列表
                     * @param methodProxy: 代理对象的方法对象
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                        //获取目标对象的方法名称
                        String methodName = method.getName();

                        System.out.println("before======"+methodName);

                        /**
                         * 调用目标对象的方法
                         */
                        //方式一:直接使用目标对象 调用 目标对象的方法
                        Object result = method.invoke(target,args);
                        //方式二:使用代理类(子类)调用 目标对象(父类)的方法
                        //invokeSuper: 调用父类的方法
                        //Object result = methodProxy.invokeSuper(proxy,args);


                        System.out.println("after====="+methodName);

                        return result;
                    }
                }
        );
    }

}

编写测试类

/**
 * 测试Cglib动态代理
 *
 */
public class Demo {

    public static void main(String[] args) {
        //1.创建目标对象
        UserServiceImpl userService = new UserServiceImpl();
        //2.创建Cglib代理
        UserServiceImpl proxy = (UserServiceImpl)LogProxy.getProxy(userService);
        //3.调用代理的方法
        proxy.save("eric",999);
        proxy.update();
        proxy.delete();
    }
}

#####AOP编程
Spring的AOP编程,底层就是用的Java动态代理模式!
1)如果目标对象实现了接口,可以使用JDK动态代理(推荐)或者Cglib动态代理
2)如果目标对象没有接口,只能使用Cglib动态代理

基本概念:连接点(JointPoint),切入点(Pointcut),通知(Advice),切面(Apsect)目标对象(Target),代理(Proxy)

Joinpoint(连接点):

在 spring 中,连接点指的都是方法(指的是那些要被增强功能的候选方法),spring 只支持方法类型
的连接点。

Pointcut(切入点):

所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。 @Pointcut(“execution(* com.huihui.*ServiceImpl.save(…))”) 之拦截了 save 方法。 save 就是切入点

Advice(通知/增强):

所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。

Weaving(织入):

织入指的是把增强用于目标对象,创建代理对象的过程。spring 采用动态代理织入,AspectJ 采用编译期织入和类装载期织入。

Target(目标对象):

被代理的对象。比如动态代理案例中的演员。

Proxy(代理):

一个类被 AOP 织入增强后,即产生一个结果代理类。比如动态代理案例中的经纪人。

Aspect(切面): 切面=切入点+通知
基于XML的AOP配置:

创建项目先导相关包:

     <!-- spring-aop sprirng自身aop编程包 -->
       <!-- spring依赖的第三方工具包(提供切入点表达式语法) -->

1.创建UserService接口和实现
2.创建切面类(需要扩展的功能)
3.配置切面类
4.测试
bean.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="userService" class="com.huihui.service.impl.UserServiceImpl"/>

    <!-- 2.创建切面对象 -->
    <bean id="logAspect" class="com.huihui.aop.LogAspect"/>

    <!-- 3.让切面对象对象目标对象进行切入(切面配置) -->
    <aop:config>

        <!-- 切面配置 = 通知(advice)+切入点(pointcut)-->
        <!--
            ref: 引用切面类对象
         -->
        <aop:aspect ref="logAspect">
            <!-- 定义切入点 -->
            <!--
                id: 定义切入点的别名
                expression: 切入点表达式(用于定义需要切入的方法)
             -->
            <aop:pointcut id="pt" expression="execution(* com.huihui.service.impl.UserServiceImpl.*(..))"/>
            <!-- 定义通知 -->
            <!--
               method: 使用切面类的哪个方法作为通知方法
               pointcut-ref: 关联切入点
             -->
            <aop:before method="writeLog" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
</beans>

####Aop编程:通知类型
前置通知:在执行目标对象方法之前执行

后置通知:在执行目标对象方法之后执行 (异常不执行)

异常通知:在执行目标对象方法发生异常时候执行

最终通知:在执行完目标对象方法后始终执行的方法就是最终通知(异常也执行)

环绕通知:

try{
​ *前置通知
​ 执行目标方法
​ *后置通知
}catch(Exception e){
​ *异常通知
}finally{

  • 最终通知
    }
测试类:
  1. 在LogAspect添加环绕通知方法
  2. bean.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">


    <bean id="userService" class="com.huihui.service.impl.UserServiceImpl"/>

    <bean id="logAspect" class="com.huihui.aop.LogAspect"/>

    <aop:config>
        <aop:aspect ref="logAspect">
        
            <aop:pointcut id="pt" expression="execution(* com.huihui.service.impl.UserServiceImpl.*(..))"/>

            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="pt"/>
            <!-- 后置通知 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pt"/>
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
            <!-- 最终通知 -->
            <aop:after method="after" pointcut-ref="pt"/>
         <!-- 环绕通知 -->
          <!-- <aop:around="around" pointcut-ref="pt"/> -->
            
        </aop:aspect>
    </aop:config>
</beans>
亦可通过注解与xml的形式进行AOP:

1.创建模块在LogAspect上添加注解

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

/**
 * 日志切面类
 */
@Component // 代替 <bean id="logAspect" class="com.huihui.aop.LogAspect"/>
@Aspect    // 代替 <aop:aspect ref="logAspect">...
public class LogAspect {

    //切入点方法
    //注意:切入点的id就是该方法的名称  (public void pt())
    //代替:<aop:pointcut id="pt" expression="execution(* com.huihui.service.impl.UserServiceImpl.*(..))"/>
    @Pointcut(value = "execution(* com.*.service.impl.UserServiceImpl.*(..))")
    public void pt(){ }
    
    
    /**
     * 前置通知
     */
    @Before("pt()")
    public void before(){
        System.out.println("前置通知=======");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pt()")
    public void afterReturning(){
        System.out.println("后置通知=======");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("pt()")
    public void afterThrowing(){
        System.out.println("异常通知======");
    }

    /**
     * 最终通知
     */
    @After("pt()")
    public void after(){
        System.out.println("最终通知=======");
    }
    /**
     * 环绕通知
     */
    @Around("pt()")
    public void around(ProceedingJoinPoint jp){
        //前置
        System.out.println("前置通知");
        //调用目标对象的方法
        try {
            jp.proceed();
            System.out.println("后置通知");
        } catch (Throwable throwable) {
            throwable.printStackTrace();

            System.out.println("异常通知");
        }finally {
            System.out.println("最终通知");
        }
    }

}

2.修改bean.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"
       xmlns:context="http://www.springframework.org/schema/context"
       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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <bean id="userService" class="com.huihui.service.impl.UserServiceImpl"/>

    <!-- IOC注解扫描 : 扫描的是@Component, @Service,@Controller, @Repistory-->
    <context:component-scan base-package="com.huihui"/>

    <!-- AOP注解扫描 : 扫描@Aspect @Pointcut @Before....  默认从整个项目扫描-->
    <aop:aspectj-autoproxy/>

</beans>

3.测试

XML方式配置

​ aop:config
​ aop:aspect
​ aop:pointcut
​ aop:before / aop:after-returning…

注解:

​ @Aspect
​ @Pointcut
​ @Before @AfterRetuning…

本文仅作个人总结!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值