三天掌握Spring系列第二讲 AOP 方法增强就是这么简单

本章讲Spring中的第二个核心AOP,学完这俩个基本思想相信你会有一个新的认识,并不会觉得它很难,反而很容易。

6、AOP概述

6.1、什么是AOP?

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。

就是说,切掉重复的,在需要使用的时候,使用动态代理的技术,使用同一份代码对需使用的方法进行增加,并且不需要修改原有代码。很酷,对不对

6.2、AOP的作用与优势

作用:

  • 在程序运行期间,不修改源码对已有方法进行增强。

优势:

  • 减少重复代码

  • 提高开发效率

  • 维护方便

6.3、AOP的实现方式

使用动态代理技术

7、AOP的具体应用

7.1、案例问题

在实现用户的 CRUD 中,其实是存在问题的,问题就是:

事务被自动控制了。换言之,我们使用了 connection 对象的 setAutoCommit(true)

此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql 语句,这种方式就无法实现功能了。

请看下面的示例:我们在业务层中多加入一个方法。

 业务层接口
 
 void transfer(String sourceName,String targetName,Float money);
 
 业务层实现类
 
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        //根据名称查询两个账户信息
        User source = userDao.findByName(sourceName);
        User target = userDao.findByName(targetName);
        //转出账户减钱,转入账户加钱
        source.setMoney(source.getMoney() - money);
        target.setMoney(target.getMoney() + money);
        //更新两个账户
        userDao.update(source);
        //模拟转账异常
        int i = 1 / 0; 
       userDao.update(target); 
   }

当我们执行时,由于执行有异常,转账失败。但是因为我们是每次执行持久层方法都是独立事务,导致无法实现事务控制(不符合事务的一致性)。会导致第一个账户钱减少,但第二个账户钱未增加。

7.2 问题的解决

解决办法:

添加一个事务控制类,让业务层来控制事务的提交和回滚。

TransactionManager 类的代码:

复制代码

 package cn.fage.manager;
 
 import javax.sql.DataSource;
 
 /**
  * @author lin
  * @version 1.0
  * @date 2020-08-11 14:45
  * @Description TODO
  */
 public class TransactionManager {
 
    private DataSource connectionUtils;
 
    public void setConnectionUtils(DataSource connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
 
    /**
      * 开启事务
      */
    public void beginTransaction(){
        try {
            connectionUtils.getConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace(); 
       } 
   }  

   /** 
     * 提交事务 
     */ 
   public void commit(){ 
       try { 
           connectionUtils.getConnection().commit(); 
       }catch (Exception e){ 
           e.printStackTrace(); 
       } 
   }  

   /** 
     * 回滚事务 
     */ 
   public void rollback(){ 
       try { 
           connectionUtils.getConnection().rollback(); 
       }catch (Exception e){ 
           e.printStackTrace(); 
       } 
   }   


   /** 
     * 释放连接 
     */ 
   public void release(){ 
       try { 
           connectionUtils.getConnection().close();//还回连接池中 
       }catch (Exception e){ 
           e.printStackTrace(); 
       } 
   } 
} 

改造业务层实现类(此处没有使用spring的Ioc)

 /**
  * 账户的业务层实现类
  *
  * 事务控制应该都是在业务层
  */
 package cn.fage.spring.impl;
 
 import cn.fage.manager.TransactionManager;
 import cn.fage.pojo.User;
 import cn.fage.spring.UserDao;
 import cn.fage.spring.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
 
 /**
  * @author lin
  * @version 1.0
  * @date 2020-08-10 11:17
  * @Description TODO
  */
 public class UserServiceImpl2 implements UserService {  

   private UserDao userDao;  

   private TransactionManager txManager;  

   @Override 
   public User saveUser(User user) { 
       return userDao.saveUser(user); 
   }  

   @Override 
   public void transfer(String sourceName, String targetName, Float money) { 
       try { 
           //1.开启事务 
           txManager.beginTransaction(); 
           //2.执行操作 
           List<User> accounts = userDao.findAllUser(); 
           //3.提交事务 
           txManager.commit(); 
       }catch (Exception e){ 
           //5.回滚操作 
           txManager.rollback(); 
           throw new RuntimeException(e); 
       }finally { 
           //6.释放连接 
           txManager.release(); 
       } 
   } 
} 

7.3 新的问题

上一小节的代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题:

业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。

试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。

我们可以使用动态代理解决这个问题

7.4 动态代理

7.4.1 动态代理的特点

字节码随用随创建,随用随加载。

它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。

装饰者模式就是静态代理的一种体现。

7.4.2 动态代理常用的有两种方式

  • 基于接口的动态代理

    • 提供者:JDK 官方的 Proxy 类。

    • 要求:被代理类最少实现一个接口。

  • 基于子类的动态代理

    • 提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。

    • 要求:被代理类不能用 final 修饰的类(最终类)。

7.2.5 解决案例中的问题

复制代码

 /**
  * 用于创建Service的代理对象的工厂
  */
 package cn.fage.factory;
 
 import cn.fage.manager.TransactionManager;
 import cn.fage.spring.UserService;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 
 /**
  * @author lin
  * @version 1.0
  * @date 2020-08-11 14:52
  * @Description 用于创建Service的代理对象的工厂
  */
 public class BeanFactory {
 
    private UserService userService;
 
    private TransactionManager txManager;
 
    public void setTxManager(TransactionManager txManager) { 
       this.txManager = txManager; 
   }   


   public final void setAccountService(UserService userService) { 
       this.userService = userService; 
   }  

   /** 
     * 获取Service代理对象 
     * 
     * @return 
     */ 
   public UserService getAccountService() { 
       return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), 
               userService.getClass().getInterfaces(), 
               new InvocationHandler() { 
                   /** 
                     * 添加事务的支持 
                     * 
                     * @param proxy 
                     * @param method 
                     * @param args 
                     * @return 
                     * @throws Throwable 
                     */ 
                   @Override 
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  

                       if ("test".equals(method.getName())) { 
                           return method.invoke(userService, args); 
                       }  

                       Object rtValue = null; 
                       try { 
                           //1.开启事务 
                           txManager.beginTransaction(); 
                           //2.执行操作 
                           rtValue = method.invoke(userService, args); 
                           //3.提交事务 
                           txManager.commit(); 
                           //4.返回结果 
                           return rtValue; 
                       } catch (Exception e) { 
                           //5.回滚操作 
                           txManager.rollback(); 
                           throw new RuntimeException(e); 
                       } finally { 
                           //6.释放连接 
                           txManager.release(); 
                       } 
                   } 
               }); 
   } 
} 

当我们改造完成之后,业务层用于控制事务的重复代码就都可以删掉了。

第8章 Spring中的AOP[掌握]

8.1 Spring中AOP的细节

8.1.1 说明

学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能。

8.1.2 AOP相关术语

  • Joinpoiont(连接点)

    所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。

  • Pointcut(切入点)

    所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。

  • Advice(通知/增强)

    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

  • Introduction(引介)

    引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

  • Target(目标对象)

    代理的目标对象。

  • Weaving(织入)

    是指把增强应用到目标对象来创建新的代理对象的过程。

    spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

  • Proxy(代理)

    一个类被 AOP 织入增强后,就产生一个结果代理类。

  • Aspect(切面)

    是切入点和通知(引介)的结合。

8.1.3 学习spring中的AOP要明确的事

a、开发阶段(我们做的)

编写核心业务代码(开发主线)

把公用代码抽取出来,制作成通知。(开发阶段最后再做)

配置文件中,声明切入点与通知间的关系,即切面。

b、运行阶段(Spring框架完成的)

Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

8.1.4 关于代理的选择

在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

8.2 基于XML的AOP配置

在学习 spring 的 aop 时,采用一个简单的账户案例作为示例。并且把 spring 的 ioc 也一起应用进来。此处只为展示AOP的配置方式所有不与数据库进行交互。

8.2.1 环境搭建

8.2.1.1 第一步:pom.xml中添加依赖

复制代码

 <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
 
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
 </dependencies>

8.2.1.2 第二步:创建spring的配置文件bean.xml并导入约束

此处要导入 aop 的约束

复制代码

 <?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">
 </beans>

8.2.1.3 第三步:配置spring的ioc

复制代码

 <!-- 配置srping的Ioc,把service对象配置进来-->
 <bean id="userService" class="cn.fage.spring.impl.UserServiceImpl"></bean>

8.2.1.4 第四步:创建通知类

复制代码

 /**
  * 用于记录日志的工具类,它里面提供了公共的代码
  */
 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方法开始记录日志了。。。"); 
   }  

   /** 
     * 环绕通知 
     * 问题: 
     *     当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 
     * 分析: 
     *     通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 
     * 解决: 
     *     Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 
     *     该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 
     * 
     * spring中的环绕通知: 
     *     它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 
     */ 
   public Object aroundPrintLog(ProceedingJoinPoint pjp){ 
       Object rtValue = null; 
       try{ 
           Object[] args = pjp.getArgs();//得到方法执行所需的参数 
           System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); 
           rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) 
           System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); 
           return rtValue; 
       }catch (Throwable t){ 
           System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); 
           throw new RuntimeException(t); 
       }finally { 
           System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); 
       } 
   } 
}

8.2.2 配置步骤

8.2.2.1 第一步:把通知类用 bean 标签配置起来

复制代码

<!-- 配置Logger类 -->
<bean id="logger" class="cn.fage.util.Logger"></bean>

8.2.2.2 第二步:使用 aop:config 声明 aop 配置

aop:config

作用:用于声明开始 aop 的配置。

复制代码

<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>

8.2.2.3 第三步:使用 aop:aspect 配置切面

aop:aspect

作用:用于配置切面。

属性:

  • id:给切面提供一个唯一标识。

  • ref:引用配置好的通知类 bean 的 id。

    <aop:aspect id="logAdvice" ref="logger">

    /aop:aspect

8.2.2.4 第四步:使用 aop:pointcut 配置切入点表达式

aop:pointcut

作用:用于配置切入点表达式,就是指定对哪些类的哪些方法进行增强。

属性:

  • expression:用于定义切入点表达式。

  • id:用于给切入点表达式提供一个唯一标识

此标签写在aop:aspect标签内部只能当前切面使用。它还可以写在aop:aspect外面,此时就变成了所有切面可用

复制代码

<aop:pointcut id="pt1" expression="execution(* cn.fage.service.impl.*.*(..))"></aop:pointcut>

8.2.2.5 第五步:使用 aop:xxx 配置对应的通知类型

aop:before

作用:用于配置前置通知,指定增强的方法在切入点方法之前执行。

属性:

  • method:用于指定通知类中的增强方法名称

  • ponitcut-ref:用于指定切入点的表达式的引用

  • poinitcut:用于指定切入点表达式

执行时间点:切入点方法执行之前执行

复制代码

<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>

aop:after-returning

作用:用于配置后置通知

属性:

  • method:用于指定通知类中的增强方法名称

  • ponitcut-ref:用于指定切入点的表达式的引用

  • poinitcut:用于指定切入点表达式

执行时间点:切入点方法正常执行之后执行。它和异常通知只能有一个执行。

复制代码

<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

aop:after-throwing

作用:用于配置异常通知

属性:

  • method:用于指定通知类中的增强方法名称

  • ponitcut-ref:用于指定切入点的表达式的引用

  • poinitcut:用于指定切入点表达式

执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个。

复制代码

<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

aop:after

作用:用于配置最终通知

属性:

  • method:用于指定通知类中的增强方法名称

  • ponitcut-ref:用于指定切入点的表达式的引用

  • poinitcut:用于指定切入点表达式

执行时间点:

无论切入点方法执行时是否有异常,它都会在其后面执行。

复制代码

aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

8.2.3 切入点表达式说明

execution:匹配方法的执行(常用)

execution(表达式)

表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

写法说明:

  • 全匹配方式

    • public void cn.fage.service.impl.UserServiceImpl.saveUser(cn.fage.pojo.User)

  • 访问修饰符可以省略

    • void cn.fage.service.impl.UserServiceImpl.saveUser(cn.fage.pojo.User)

  • 返回值可以使用*号,表示任意返回值

    • * cn.fage.service.impl.UserServiceImpl.saveUser(cn.fage.pojo.User)

  • 包名可以使用

    号,表示任意包,但是有几级包,需要写几个

    • * *.*.*.*.UserServiceImpl.saveUser(cn.fage.pojo.User)

  • 使用..来表示当前包,及其子包

    • * cn..UserServiceImpl.saveUser(cn.fage.pojo.User)

  • 类名可以使用*号,表示任意类

    • * cn..*.saveUser(cn.fage.pojo.User)

  • 方法名可以使用*号,表示任意方法

    • * cn..*.*( cn.fage.pojo.User)

  • 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数

    • * cn..*.*(*)

  • 参数列表可以使用..表示有无参数均可,有参数可以是任意类型

    • * cn..*.*(..)

  • 全通配方式

    • * *..*.*(..)

注:

通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。

execution(``* cn.fage.service.impl.*.*(..)``)

8.2.4 环绕通知

复制代码

<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>

aroundPrintLog方法具体注释在通知类中

aop:around :

作用:用于配置环绕通知

属性:

  • method:用于指定通知类中的增强方法名称

  • ponitcut-ref:用于指定切入点的表达式的引用

  • poinitcut:用于指定切入点表达式

说明:

它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。

注意:

通常情况下,环绕通知都是独立使用的

8.3 基于注解的AOP配置

8.3.1 环境搭建

8.3.1.1 第一步:和 准备必要的代码和配置pom.xml

利于基于XML的AOP配置中的代码及pom.xml即可

8.3.1.2 第二步:在配置文件中导入 context 的名称空间

复制代码

<?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">

</beans>

8.3.1.3 第三步:把资源使用注解配置

复制代码

/**
* 账户的业务层实现类
*/
@Service("usertService")
public class UserServiceImpl implements UserService{

}

8.3.1.4 第四步:在配置文件中指定 spring 要扫描的包

复制代码

<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="cn.fage"></context:component-scan>

8.3.2 配置步骤

8.3.2.1 第一步:把通知类也使用注解配置

复制代码

@Component("logger")
public class Logger {

}

8.3.2.2 第二步:在通知类上使用@Aspect 注解声明为切面

作用:

把当前类声明为切面类。

复制代码

@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

}

8.3.2.3 第三步:在增强的方法上使用注解配置通知

@Before

作用:把当前方法看成是前置通知。

属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

复制代码

/**
* 前置通知
*/
@Before("execution(* cn.fage.service.impl.*.*(..))")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}

@AfterReturning

作用:把当前方法看成是后置通知。

属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

复制代码

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

@AfterThrowing

作用:把当前方法看成是异常通知。

属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

复制代码

/**
* 异常通知
*/
@AfterThrowing("execution(* cn.fage.service.impl.*.*(..))")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}

@After

作用:把当前方法看成是最终通知。

属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

复制代码

/**
* 最终通知
*/
@After("execution(* cn.fage.service.impl.*.*(..))")
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}

8.3.2.4 第四步:在 spring 配置文件中开启 spring 对注解 AOP 的支持

复制代码

<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

8.3.3 环绕通知注解配置

@Around

作用:把当前方法看成是环绕通知。

属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

复制代码

@Around("execution(* cn.fage.service.impl.*.*(..))")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数

System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
thrownewRuntimeException(t);
}finally{
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}}

8.3.4 切入点表达式注解

@Pointcut

作用:指定切入点表达式

属性:value:用于指定表达式的内容

复制代码

@Pointcut("execution(* cn.fage.service.impl.*.*(..))")
private void pt1() {}

引用方式:

原环绕通知注释

复制代码

@Around("execution(* cn.fagea.service.impl.*.*(..))")

在设置Pointcut后,与上面作用相同。

复制代码

@Around("pt1()")//注意,千万别忘了写括号

8.3.5 不使用XML的配置方式

复制代码

@Configuration
@ComponentScan(basePackages="cn.fage")
@EnableAspectJAutoProxy //开启 spring 对注解 AOP 的支持
public class SpringConfiguration {
}

测试

package cn.fage.test.aop;

import cn.fage.configuration.ApplicationContextUtil;
import cn.fage.configuration.SpringConfiguration;
import cn.fage.pojo.User;
import cn.fage.spring.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author lin
* @version 1.0
* @date 2020-08-11 15:23
* @Description TODO
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=SpringConfiguration.class)
publicclassRunnerTest{

@Test
publicvoidtest01(){
ApplicationContextcontext=ApplicationContextUtil.getApplicationContext();
for(Stringname:context.getBeanDefinitionNames()){
System.out.println(name);
}
UserServiceuserService=(UserService)context.getBean("userService");
userService.saveUser(null);
}}


运行结果如下:

连接到目标VM, 地址: ''127.0.0.1:59765', transport: '套接字'', 传输: '{1}'
8月 11, 2020 3:33:41 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener,org.springframework.test.context.support.DirtiesContextTestExecutionListener,org.springframework.test.context.transaction.TransactionalTestExecutionListener,org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener,org.springframework.test.context.event.EventPublishingTestExecutionListener]
8月11,20203:33:41下午org.springframework.test.context.support.AbstractTestContextBootstrappergetTestExecutionListeners
信息:UsingTestExecutionListeners:[org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@1283bb96,org.springframework.test.context.support.DependencyInjectionTestExecutionListener@74f0ea28,org.springframework.test.context.support.DirtiesContextTestExecutionListener@f6efaab,org.springframework.test.context.transaction.TransactionalTestExecutionListener@3c19aaa5,org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@3349e9bb,org.springframework.test.context.event.EventPublishingTestExecutionListener@409bf450]
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfiguration
applicationContextUtil
dataSourceConfigs
userConfigs
userDao
userService
logger
dataSource1
dataSource2
user2
user
org.springframework.aop.config.internalAutoProxyCreator
Logger类中的aroundPringLog方法开始记录日志了。。。前置
前置通知Logger类中的beforePrintLog方法开始记录日志了。。。
Logger类中的aroundPringLog方法开始记录日志了。。。前置
前置通知Logger类中的beforePrintLog方法开始记录日志了。。。
org.springframework.jdbc.datasource.DriverManagerDataSource@36b0fcd5savenull
后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。
最终通知Logger类中的afterPrintLog方法开始记录日志了。。。
Logger类中的aroundPringLog方法开始记录日志了。。。后置
Logger类中的aroundPringLog方法开始记录日志了。。。最终
后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。
最终通知Logger类中的afterPrintLog方法开始记录日志了。。。
Logger类中的aroundPringLog方法开始记录日志了。。。后置
Logger类中的aroundPringLog方法开始记录日志了。。。最终
与目标VM断开连接,地址为:''127.0.0.1:59765',transport:'套接字'',传输:'{1}'

进程已结束,退出代码0

代码结构

扩展,获取ApplicationContext上下文和动态扩展自定义bean

package com.ba.cloud.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
importorg.springframework.stereotype.Component;

importjavax.sql.DataSource;

@Component
publicclassApplicationContextProvider
implementsApplicationContextAware{

@Autowired
DynamicDataSourceConfigdynamicDataSourceConfig;
@Autowired
DataSourcedataSource;

publicstaticDataSourcedataSourceHelp;

/**
*上下文对象实例
*/
privatestaticApplicationContextapplicationContext;

@Override
publicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{
ApplicationContextProvider.applicationContext=applicationContext;
dataSourceHelp=dataSource;
ConfigurableApplicationContextconfigurableApplicationContext=(ConfigurableApplicationContext)applicationContext;
//获取bean工厂并转换为DefaultListableBeanFactory
DefaultListableBeanFactorydefaultListableBeanFactory=(DefaultListableBeanFactory)configurableApplicationContext.getBeanFactory();
for(DynamicDataSourceConfig.DataSourcedatasouce:dynamicDataSourceConfig.getDatasouces()){
DruidDataSourcedruidDataSource=newDruidDataSource();
druidDataSource.setUsername(datasouce.getUsername());
druidDataSource.setPassword(datasouce.getPassword());
druidDataSource.setUrl(datasouce.getUrl());
druidDataSource.setDriverClassName(datasouce.getDriver());
//通过BeanDefinitionBuilder创建bean定义
BeanDefinitionBuilderbeanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(DataSource.class);
//注册bean
defaultListableBeanFactory.registerBeanDefinition(datasouce.getName(),beanDefinitionBuilder.getRawBeanDefinition());
defaultListableBeanFactory.registerSingleton(datasouce.getName(),druidDataSource);
}
}

/**
*获取applicationContext
*
*@return
*/
publicstaticApplicationContextgetApplicationContext(){
returnapplicationContext;
}

/**
*通过name获取Bean.
*
*@paramname
*@return
*/
publicstaticObjectgetBean(Stringname){
returngetApplicationContext().getBean(name);
}

/**
*通过class获取Bean.
*
*@paramclazz
*@param<T>
*@return
*/
publicstatic<T>TgetBean(Class<T>clazz){
returngetApplicationContext().getBean(clazz);
}

/**
*通过name,以及Clazz返回指定的Bean
*
*@paramname
*@paramclazz
*@param<T>
*@return
*/
publicstatic<T>TgetBean(Stringname,Class<T>clazz){
returngetApplicationContext().getBean(name,clazz);
}

}

 

微信公众号点击关于我,加入QQ群,即可获取到代码

 

公众号:发哥讲

这是一个稍偏基础和偏技术的公众号,甚至其中包括一些可能阅读量很低的包含代码的技术文,不知道你是不是喜欢,期待你的关注。

img

如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈~

● 扫码关注我们

据说看到好文章不推荐的人,服务器容易宕机!

本文版权归 发哥讲 和 博客园 共有,原创文章,未经允许不得转载,否则保留追究法律责任的权利。

posted @ 2020-08-11 16:40  发哥讲Java  阅读(510)  评论(0)    收藏  举报