Spring系统学习--3AOP

 

 解决什么样的问题?

@Override 
public void transfer(String sourceName,String targetName,Float money){ //1.根据名称查询转出账户 Account source=accountDao.findByName(sourceName); //2.根据名称查询转入账户 Account target=accountDao.findByName(targetName); //3.转出账户减钱 source.setMoney(source.getMoney()-money); //4.转入账户加钱 target.setMoney(target.getMoney()+money); //5.更新转出账户 accountDao.update(source); //模拟转账异常 int i=1/0;//6.更新转入账户 accountDao.update(target);

 动态代理

  1.基于接口

/*创建一个被代理的类(这里加入是笔记本电脑生产厂家),被代理类要实现代理类接口*/
public class ComputerFactory implements IAgent{
    public void sale(Float money){
        System.out.println("一手交钱"+money+"一手交货");
    }
    public void afterService(Float money){
        System.out.println("维想修,拿钱来"+money);
    }
}
/*创建一个代理类(接口),(这里假设是生产厂家的代理,帮着买电脑等...)*/

public interface IAgent {
    public void sale(Float money);
    public void afterService(Float money);
}
/*下面是如何使用代理类代理处理业务的*/
package spring.day03.agent;

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

public class Demo {
    /**
     * 动态代理
     * 特点:字节码随用随创建,随用随加载
     * 分类:
     * 基于接口的
     * 基于子类的
     * 作用:
     * 在不改变源码的基础上对已有方法增强
     * 此类讲解的是基于接口的动态代理
     * 提供者:JDK官方
   * 涉及的类:Proxy * 创建代理对象的方法:newProxyInstance
   * 方法的参数: * ClassLoader:类加载器。和被代理象使用相同的类加载器。该参数是固定写法。 * Class[]:字节码数组。和被代理对象具有相同的行为,实现相同的接口。 * InvocationHandler:它是一个接口。提供如何代理的代码。也就是增强的代码。该参数一般都成名内部类。 * 并且它是策略模式的体现。 * 策略模式: * 要求:数据已经有了,目的明确。 * 达成目标的过程就是策略。 * 该参数是谁用谁写。 * 使用要求:被代理类最少实现一个接口。
*/ public static void main(String[] args) { final ComputerFactory factory = new ComputerFactory(); IAgent proxyFactory = (IAgent) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), new InvocationHandler() { /** *该方法的特征: *执行被代理对象的任何方法,都会经过该方法。该方法有拦截的特点 *方法的参数: *Object proxy:代理对象的引用。 *Method method:当前执行的方法 *Object[]args:当前执行方法所需的参数 *方法的返回值: *Object:和被代理对象的方法返回值一致。 * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object rtValue = null; //1.获取正在执行方法的参数 Float money = (Float) args[0]; //2.判断正在执行的方法 if ("sale".equals(method.getName())) { //经销商最少一台电脑挣2000元钱 if (money > 7000) { rtValue = method.invoke(factory, money / 2); } } if ("afterservice".equals(method.getName())) { //经销商最少一台电脑正600元 if (money > 1600) { rtValue = method.invoke(factory, money / 4 * 3); } } return rtValue; // return method.invoke(factory,args); } }); // factory.sale(5000f); // factory.afterService(100f); proxyFactory.sale(10000f); proxyFactory.afterService(100f); } }

  2.基于子类

  
<!--所需依赖-->
  <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.2.2</version>
    </dependency>
    <dependency>
      <groupId>asm</groupId>
      <artifactId>asm</artifactId>
      <version>3.3.1</version>
    </dependency>
/*被代理的类*/
public class ComputerFactory {
    public void sale(Float money){
        System.out.println("一手交钱"+money+"一手交货");
    }
    public void afterService(Float money){
        System.out.println("维想修,拿钱来"+money);
    }
}
/*执行代理的代码*/
public class Demo {
    /**
     * 动态代理
     * 特点:字节码随用随创建,随用随加载
     * 分类:
     * 基于接口的
     * 基于子类的
     * 作用:
     * 在不改变源码的基础上对已有方法增强
     * 此类讲解的是基于子类的动态代理
     * 提供者:cglib
     * 依赖外部jar:cglib;asm;
     * 涉及的类:Enhancer
     * 创建代理对象的方法:create
     * 方法的参数:
     * Class:字节码。指定被代理文件的字节码文件
     * CallBack:如何代理。
     * <p>
     * 使用要求:被代理类不能是最终类
     */
    public static void main(String[] args) {
        ComputerFactory factory = new ComputerFactory();
        ComputerFactory cglibfactory = (ComputerFactory) Enhancer.create(factory.getClass(), new MethodInterceptor() {
            /**
             *它和InvocationHandler中的invoke方法作用是一样的
             *该方法的前3个参数和invoke方法的三个参数作用是一样的。
             *该方法的返回值和invoke方法的返回值作用也是一样的。
             *
             *MethodProxy参数:它是当前执行方法的代理对象,一般用不到。
             */
            @Override
            public Object intercept(Object Proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                Object rtValue = null;
                //1.获取正在执行方法的参数
                Float money = (Float) args[0];
                //2.判断正在执行的方法
                if ("sale".equals(method.getName())) {
                    //经销商最少一台电脑挣2000元钱
                    if (money > 7000) {
                        rtValue = method.invoke(factory, money / 2);
                    }
                }
                if ("afterService".equals(method.getName())) {
                    //经销商最少一台电脑正600元
                    if (money > 1600) {
                        rtValue = method.invoke(factory, money / 4 * 3);
                    }
                }
                return rtValue;
//                return method.invoke(factory,args);
            }
        });
        cglibfactory.sale(10000f);
        cglibfactory.afterService(5000f);
    }
}

AOP概述

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

作用:
在程运行期间:不修改源码对已有方法进行增强。
优点:
减少重复代码
提高开发效案
维护方便

 

 

 

 基于XML的AOP配置

项目依赖:

        <!--    junit:方便我们进行方法的测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <!--      <scope>test</scope>-->
        </dependency>
        <!--  spring-context:导入spring核心jar包  -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.7.RELEASE</version>
            <!--      <scope>test</scope>-->
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <!--    导入日志jar包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>1.3.4</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>asm</groupId>
            <artifactId>asm</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>

SpringXML配置文件:

<?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="AccountService" class="main.java.spring.day03.aop.impl.AccountServiceImpl"></bean>
    <!--spring基于xml的aop配置步骤:
    前期准备:
    1.拷贝aop的jar包
    2.在配置文件中导入aop的约束
    配置步骤:
        1.把通知bean也交给spring来管理
        2.使用aop名称空间下的aop:config标签开始aop的配置
        3.使用aop:aspect标签,开始配置切面。
            id属性:用于给切面提供一个唯一标识
            ref属性:用于引用通知bean的id。
        4.使用aop:before标签配置前置通知
            method属性:用于指定通知类中的哪个方法是前置通知
            pointcut属性:用于指定切入点表达式。
                切入点表达式:
                    关键字:execution(表达式)
                表达式的写法:
                    访问修饰符返回值包名。包名...类名.方法名(参数列表)
                    全匹配方式:
                        public vdid main.java.spring.day03.aop.impl.AccountServiceImpl.saveAccount()
                    访问修饰符可以省略:
                                void main.java.spring.day03.aop.impl.AccountServiceImpl.saveAccount()
                    返回值可以使用*,表示任意返回值类型
                        * main.java.spring.day03.aop.impl.AccountServiceImpl.saveAccount()
                    包名可以使用*,表示任意包。但是有几级包,需要写几个*
                        * *,*,*,*,*,*.AccountServiceImpl.saveAccount()
                    包名可以使用..表示当前包及其子包:
                        * *..AccountServiceImpl.saveAccount()
                    类名可以使用*,表示任意类
                        **..*.saveAccount()
                    方法名可以使用*,表示任意方法
                        *main.java.spring..*.*()
                    参数列表可以指定具体类型:
                        基本类型直接写类型名称:*main.java.spring..*.*(int)
                        引用类型必须是包名,类名的方式:*main.java.spring..*.*(java.lang.String)
                    参数列表可以使用*,表示任意参数类型,但是必须有参数
                            *main.java.spring..*.*(*)
                    参数列表可以使用..,表示有无参数均可:
                            *main.java.spring..*.*(..)
  -->
    <bean id="logger" class="main.java.spring.day03.aop.Logger"></bean>
    <aop:config>
        <!--使用aop:pointcut标签可以配置通用切入点表达式 ;写在切面aop:aspect标签内部,只能当前切面使用。
  如果要想所有切面使用,请写到aop:aspect标签外面-->
        <aop:pointcut expression="execution(* main.java.spring..*.*(..)) " id="pt"/>
        <aop:aspect id="logerAop" ref="logger">
            <!--        配置前置通知:她永远都会在切入点方法执行之前执行-->
            <aop:before method="beforeprintLog"
                        pointcut="execution(public void main.java.spring.day03.aop.impl.AccountServiceImpl.saveAccount())"></aop:before>
            <!--配置后置通知:当切入点方法正常执行之后,后置通知执行。它和异常通知只能有一个执行-->
            <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* main.java.spring..*.*(..))"/>
            <!--配置异常通知:当切入点方法执行产生异常后执行。它和后置通知是互斥的-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* main.java.spring..*.*(..))"/>
            <!--配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行-->
            <aop:after method="afterPrintLog" pointcut="execution (* main.java.spring..*.*(..))"/>
            <!--配置环绕通知:详细注释写到Logger类里了-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt"/>
        </aop:aspect>

    </aop:config>
</beans>
/*
业务层代码
*/
public interface AccountService {
    /**
     * 保存账户
     */
    void saveAccount();
}

/*
业务层实现类
 */
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void saveAccount() {
        System.out.println("保存账户!!!");
//        int a=1/0;//模拟异常
    }
}
/*****************切面类*************************/
package main.java.spring.day03.aop;

import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;

public class Logger {
    /**
     *计划让其在切入点方法执行之前执行
     */
    public void beforeprintLog() {
        System.out.println("Logger类中的beforePrintLog方法开始记录日志了。。。前置");
    }

    /**
     * 后置通知
     */
     public void afterReturningPrintLog(){
     System.out.println("Logger类中的afterReturningPrintLog方法开始记录日志了。。。后置");
     }

     /**
     *异常通知
     */
    public void afterThrowingPrintLog(){
        System.out.println("Logger类中的afterThrowingPrintLog方法开始记录日志了。。。异常");
    }
    /**
     *最终通知
     */
    public void afterPrintLog() {
        System.out.println("Logger类中的afterPrintLog方法开始记录日志了。。。最终");

    }

    /**
     *环绕通知
     * 问题:
     * 当我们配置了环绕通知之后,执行切入点方法时,最终的结果是环绕通知的代码执行了,而切入点方法却没有执行。
     * 分析:
     * 根据动态代理的代码分析,可以看到invoke方法中有一个明确调用切入点方法的代码。而我们spring中的环绕通知目前没有调用切入点方法
     * 解决办法:
     *  思路:我们也需要在环绕通知中明确调用一下切入点方法。
     *  Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口可以作为环绕通知的方法参数来使用。
     *  在程序运行时spring框架会为我们注入该接口的实现类供我们使用。I
     *  该接口有个方法:
     *             proceed()方法,它就相当于明确调用切入点方
     *   环绕通知:它是spring为我们提供的一种可以在代码中手动控制通知何时执行的方式方式。
     */

    public Object aroundPrintLog(MethodInvocationProceedingJoinPoint pjp) {
        //获取方法所需参数
        Object[] args = pjp.getArgs();
        Object rtValue = null;
        try {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。前置");
            rtValue = pjp.proceed(args);
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。后置");
        } catch (Throwable throwable) {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。异常");
            throwable.printStackTrace();
        }finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。最终");
        }
        return rtValue;
    }

}
/******************测试类**************/
public class Client {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config/bean_aop.xml");
        AccountServiceImpl accountService = context.getBean("AccountService", AccountServiceImpl.class);
        accountService.saveAccount();
        context.close();
    }
}

 

基于注解的AOP配置

项目依赖:

 <!--    junit:方便我们进行方法的测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <!--      <scope>test</scope>-->
        </dependency>
        <!--  spring-context:导入spring核心jar包  -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.2.RELEASE</version>
            <!--      <scope>test</scope>-->
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <!--    导入日志jar包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>1.3.4</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>asm</groupId>
            <artifactId>asm</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>

spring配置类

/**
 * 创建spring的配置类,相当于原来的bean.xml
 */
@Configuration
@ComponentScan("spring")//配置要扫描的包
@EnableAspectJAutoProxy//开启spring对注解AOP的支持
public class SpringConfigtion {

}

业务层实现类

/*
业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {


    @Override
    public void saveAccount() {
        System.out.println("保存账户!!!");
//        int a=1/0;//模拟异常
    }
}

切面类

@Component("logger")
@Aspect//把此类配置成一个切面
public class Logger {
    @Pointcut("execution(* spring..*.*(..))")
    public void pt(){

    }
    /**
     * 前置通知
     *计划让其在切入点方法执行之前执行
     */
    @Before(value = "pt()")
    public void beforeprintLog() {
        System.out.println("Logger类中的beforePrintLog方法开始记录日志了。。。前置");
    }

    /**
     * 后置通知
     */
    @AfterReturning(value = "pt()")
     public void afterReturningPrintLog(){
     System.out.println("Logger类中的afterReturningPrintLog方法开始记录日志了。。。后置");
     }

     /**
     *异常通知
     */
     @AfterThrowing(value = "pt()")
    public void afterThrowingPrintLog(){
        System.out.println("Logger类中的afterThrowingPrintLog方法开始记录日志了。。。异常");
    }
    /**
     *最终通知
     */
    @After(value = "pt()")
    public void afterPrintLog() {
        System.out.println("Logger类中的afterPrintLog方法开始记录日志了。。。最终");

    }


//    @Around(value = "pt()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        //获取方法所需参数
        Object[] args = pjp.getArgs();
        Object rtValue = null;
        try {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。前置");
            rtValue = pjp.proceed(args);
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。后置");
        } catch (Throwable throwable) {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。异常");
            throwable.printStackTrace();
        }finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。最终");
        }
        return rtValue;
    }

}

测试类

public class Client {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfigtion.class);
        AccountService accountService = (AccountService)context.getBean("accountService");
        accountService.saveAccount();
        context.close();
    }
}

 

posted @ 2020-07-03 11:22  指尖下的世界  阅读(260)  评论(0编辑  收藏  举报
/* 看板娘 */ /*炸泡*/
/* 鼠标点击求赞文字特效 */