Spring 04 -SpringAOP开发

Posted 凌晨五点深蓝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 04 -SpringAOP开发相关的知识,希望对你有一定的参考价值。

SpringAOP开发

面向切面编程,在不修改源代码的情况加,对类功能实现增强


SpringAOP

1 原理

AOP原理

2 动态代理

Spring的底层技术就是动态代理

  • 动态代理分类
    • JDK动态代理
    • Cglib动态代理

2.1 JDK动态代理

接口:定义代理对象和被代理对象需要做的事情

//定义代理类和被代理类共同做的事情
public interface Subject 
    void saleHouse();

实现类:目标对象(被增强对象)

public class RealSubject implements Subject
    @Override
    public void saleHouse() 
        System.out.println("买房子....");
    

代理工厂

public class JDKProxyFactory 

    /**
     * 使用jdk动态代理,获取代理对象
     * @param obj   被代理对象
     * @return      代理对象
     */
    public static Object getProxy(Object obj)
        //类加载器
        ClassLoader classLoader = JDKProxyFactory.class.getClassLoader();
        //被代理对象所实现的接口
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        //对对象的方法调用的拦截
        InvocationHandler invocationHandler = new InvocationHandler() 
            /**
             *
             * @param proxy             代理对象
             * @param method            拦截方法
             * @param args              拦截方法的参数
             * @return                  返回的是该方法的返回值
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
                //做增强工作
                System.out.println("找客户。。。。");
                Object invoke = method.invoke(obj, args);
                System.out.println("办手续。。。。");
                return invoke;
            
        ;
        Object proxyInstance = 
            Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxyInstance;
    

测试

@Test
public void test01()
    //通过代理工厂获取代理对象,通过代理对象调用方法
    Subject proxy = (Subject) JDKProxyFactory.getProxy(new RealSubject());
    proxy.saleHouse();
    //说明代理对象不属于RealSubject类,而是Subject接口的实现类
    System.out.println(proxy instanceof RealSubject); //false

2.2.2 Cglib动态代理

类:目标对象(被增强对象) 不需要实现接口

public class RealSubject
    public void saleHouse() 
        System.out.println("买房子....");
    

动态代理工厂

public class CglibProxyFactory 
    public static Object getProxy(Object obj)
        //创建Cglib代理工具类对象
        Enhancer enhancer = new Enhancer();
        //设置代理对象的父类(代理对象和被代理对象是继承关系)
        enhancer.setSuperclass(obj.getClass());
        Callback callback = new MethodInterceptor() 
            /**
             *
             * @param o                代理对象
             * @param method           拦截的方法
             * @param objects          拦截的方法参数
             * @param methodProxy      方法的代理对象
             * @return                 方法的返回值
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, 
                                    MethodProxy methodProxy) throws Throwable 
                
                //做增强工作
                System.out.println("找客户cglib。。。。");
                Object invoke = method.invoke(obj, objects);
                System.out.println("办手续cglib。。。。");
                return invoke;
            
        ;
        //拦截被代理对象的方法
        enhancer.setCallback(callback);
        //创建代理对象
        Object o = enhancer.create();
        return o ;
    

测试

 @Test
public void test02()
    //被代理对象
    RealSubject realSubject = new RealSubject();
    //通过代理工厂获取代理对象,通过代理对象调用方法
    //代理对象
    RealSubject proxy = (RealSubject) CglibProxyFactory.getProxy(realSubject);
    proxy.saleHouse();
    //说明代理对象属于RealSubject类,而且就是RealSubject类的子类
    System.out.println(proxy instanceof RealSubject); //true

2.2.3 JDK动态代理和Cglib动态代理

JDK动态代理和Cglib动态代理

  • 两者都可以实现产生代理对象
  • JDK动态代理是java原生自带的,Cglib动态代理是第三方的
  • JDK动态代理实现代理:代理对象和目标对象之间实现同一个接口
  • Cglib动态代理实现代理:代理对象是目标对象的子类

3 SpringAOP

3.1 AOP专业术语

  • 连接点(Joinpoint): 表示一切能被增强方法
  • 切入点(Pointcut): 被增强的方法 <aop:pointcut id="pc" expression="execution(* com.ying.service..*.*(..))"/>
  • 通知、增强(Advice): 增强的功能(通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知) <aop:before method="before" pointcut-ref="pc"/>
  • 目标对象(Target): 被代理对象
  • 织入(Weaving): 将通知加入连接点的过程 aop:aspect ref=""里面的代码过程
  • 代理(Proxy): 代理对象
  • 切面(Aspect): 连接点被增强的内容称之为切面(切入点+通知) aop:aspect ref=""

3.2 环境搭建

导入依赖 spring-aspects

<!-- springAOP切面开发的依赖  -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.6</version>
</dependency>

3.3 基于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"
       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 https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.ying.aop"/>
   <!-- SpringAOP的配置   -->
    <!-- 创建增强类   -->
    <bean id="advice" class="com.ying.advice.MyAdvice"></bean>
    <aop:config>
        <!-- SpringAOP的切面   -->
        <aop:aspect ref="advice">
            <!-- SpringAOP切入点 id:切入点的唯一标识   expression:切入点表达式 -->
            <!--
                  切入点表达式
                  方法的全称
                  public void com.ying.service.impl.UserServiceImpl.addUser();
                  * com.ying.service.impl.UserServiceImpl.addUser();
                  * com.ying.service..*.*(..);
				  * com.ying.service.*.*Impl.*();
            -->
            <aop:pointcut id="pc" expression="execution(* com.ying.service..*.*(..))"/>
            <!-- SpringAOP的通知配置  -->
			<!-- <aop:before method="before" pointcut-ref="pc"></aop:before>-->
			<!-- <aop:after method="after" pointcut-ref="pc"></aop:after>-->
   			<!-- <aop:after-throwing method="throwing" pointcut-ref="pc"/>-->
			<!-- <aop:after-returning method="returning" pointcut-ref="pc"/>-->
            <aop:around method="around" pointcut-ref="pc"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

增强(通知)类

//增强类
public class MyAdvice 
    
//    public void before()
//        System.out.println("前置通知(在目标对象的方法之前执行)");
//    
//    public void after()
//        System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
//    
//    public void throwing()
//        System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
//    
//    public void returning()
//        System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
//    
    public Object around(ProceedingJoinPoint joinPoint)
        try 
            System.out.println("前置通知(在目标对象的方法之前执行)");
            //System.out.println("方法本身");
            //执行目标对象的方法
            Object o = joinPoint.proceed();
            //获取到目标对象
            Object target = joinPoint.getTarget();
            System.out.println(target);
            //获取目标对象的方法
            Signature signature = joinPoint.getSignature();
            System.out.println(signature.getName());
            System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
            //通过调用目标对象的方法获取到返回值,并继续返回
            return o;
         catch (Throwable e) 
            e.printStackTrace();
            System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
         finally 
            System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
        
        return null;
    

测试 依赖 spring-test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AOPTest 
    @Autowired
    private UserService userService;
    @Test
    public void test01()
        int count = userService.addUser();
        System.out.println(count);
        //userService.deleteUser();
    

结果

前置通知(在目标对象的方法之前执行)
addUser...
com.ying.service.impl.UserServiceImpl@543588e6
addUser
后置通知(在目标对象的方法之后执行(如果有异常则不会执行))
最终通知(在目标对象的方法之后执行(有没有异常都会执行))
100

3.4 基于注解配置

xml配置文件

<!-- 开启注解扫描 -->
<context:component-scan base-package="com.ying"/>
<!-- 开启AOP的注解扫描   -->
<aop:aspectj-autoproxy/>

增强类

@Component
@Aspect   //设置为切面
public class MyAdvice2 
    @Pointcut("execution(* com.ying.service..*.*(..))")
    public void pc()

    @Before("pc()")
    public void before()
        System.out.println("前置通知(在目标对象的方法之前执行)");
    
    @After("execution(* com.ying.service..*.*(..))")
    public void after()
        System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
    
    @AfterThrowing("execution(* com.ying.service..*.*(..))")
    public void throwing()
        System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
    
    @AfterReturning("execution(* com.ying.service..*.*(..))")
    public void returning()
        System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
    

//    @Around("execution(* com.ying.service..*.*(..))")
//    public Object around(ProceedingJoinPoint joinPoint)
//        try 
//            System.out.println("前置通知(在目标对象的方法之前执行)");
//            //System.out.println("方法本身");
//            //执行目标对象的方法
//            Object o = joinPoint.proceed();
//            //获取到目标对象
//            Object target = joinPoint.getTarget();
//            System.out.println(target);
//            //获取目标对象的方法
//            Signature signature = joinPoint.getSignature();
//            System.out.println(signature.getName());
//            System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
//            //通过调用目标对象的方法获取到返回值,并继续返回
//            return o;
//         catch (Throwable e) 
//            e.printStackTrace();
//            System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
//         finally 
//            System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
//        
//        return null;
//    

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class AOPTest 
    @Autowired
    private UserService userService;
    @Test
    public void test01()
        userService.deleteUser();
    

结果

前置通知(在目标对象的方法之前执行)
deleteUser...
后置通知(在目标对象的方法之后执行(如果有异常则不会执行))
最终通知(在目标对象的方法之后执行(有没有异常都会执行))

2.5 通知类型

  • 环绕通知
    • public Object around(ProceedingJoinPoint joinPoint)
    • 调用目标对象的方法joinPoint.proceed();
  • 四种通知:
    • 前置通知【在调用目标方法之前执行】(开启事务)
    • 最终通知【在调用目标方法之后执行(无论是否有错都会执行)】(释放资源)
    • 异常通知【在调用目标方法报错执行】(回滚事务)
    • 后置通知【在调用目标方法之后执行】(提交事务)

Spring DAO vs Spring ORM vs Spring JDBC

Pat 的疑惑

最近关注于 Spring 提供的数据访问技术,对于 Spring 相关的这几个项目有何不同我不是太明白:

个人理解,Spring JDBC 提供了可以减少自己写 SQL 查询来访问数据库的样板代码的模板。
Spring-ORM 提供了通过 ROM 技术访问数据库的简化样板,比如 Hibernate,My(i)Batis 等等。
Spring 官网介绍的 Spring-DAO:Spring 提供的数据访问对象(DAO)技术旨在以一个一致的方式轻松使用不同的数据访问技术诸如 JDBC,Hibernate 或者 JDO。
关于 ORM vs JDBC 我清楚一点,因为它们都用于以不同的方式访问数据库。但对于 Spring-DAO 我是晕头转向了。
有没有朋友把这三者之间的区别帮忙澄清一下?每个应该首选应用于什么场景?
此外,Spring 现在还出了另外一个项目 Spring-DATA,它是 Spring 提供的所有数据访问技术的一种父项目,还是只是 Spring-DAO 的一个新名字?

Gaetan 的解释

关于以上提到的每个技术的解释。

Spring-DAO

Spring-DAO 并非 Spring 的一个模块,它实际上是指示你写 DAO 操作、写好 DAO 操作的一些规范。因此,对于访问你的数据它既没有提供接口也没有提供实现更没有提供模板。在写一个 DAO 的时候,你应该使用 @Repository 对其进行注解,这样底层技术(JDBC,Hibernate,JPA,等等)的相关异常才能一致性地翻译为相应的 DataAccessException 子类。
举个例子,假设你正在使用 Hibernate,你的服务层捕获 HibernateException 以进行相关处理。如果你改为 JPA,你的 DAO 接口不应该也发生改变,服务层仍旧编译捕捉 HibernateException 的代码块,但是你永远不会再进入这些代码块,因为你的 DAO 现在抛出的是 JPA 的 PersistenceException。通过在你的 DAO 上使用 @Repository,底层技术相关的异常统统解释为 Spring 的 DataAccessException;你的服务层能够捕获到这些异常,而且如果你想要更换持久层技术,因为 Spring 翻译了本地异常所以仍然会抛出同样的 Spring DataAccessException。
但注意由于以下原因这种用法是受限的:

  1. 你不应该在持久层捕捉异常,因为底层技术可能已经将事务进行了回滚(取决于具体的 exception 类型),因此你不应该再(在持久层)继续执行你的备用方案。
  2. 底层技术提供的异常级别往往比 Spring 提供的更加丰富,而且底层技术之间也没有任何限定来对此进行匹配。这样的依赖是有风险的。但是对你的 DAO 类使用 @Repository 进行注解仍然不失为一个好的做法,因为这些 bean 会自动被扫描程序添加。此外,Spring 可能还会给这一注解添加其他有用特性。

Spring-JDBC

Spring-JDBC 提供了 Jdbc 模板类,它移除了连接代码以帮你专注于 SQL 查询和相关参数。你只需给它配置一个 DataSource,然后你就可以这样进行写代码了:

[java] view plain copy
 
 print?
  1. int nbRows = jdbcTemplate.queryForObject("select count(1) from person", Integer.class);  
  2.   
  3.   
  4. Person p = jdbcTemplate.queryForObject("select first, last from person where id=?",   
  5.              rs -> new Person(rs.getString(1), rs.getString(2)),   
  6.              134561351656L);  


Spring-JDBC 还提供了一个 JdbcDaoSupport,这样你可以对你的 DAO 进行扩展开发。它主要定义了两个属性:一个 DataSource 和一个 JdbcTemplate,它们都可以用来实现 DAO 方法。JdbcDaoSupport 还提供了一个将 SQL 异常转换为 Spring DataAccessExceptions 的异常翻译器。
如果你计划使用纯 jdbc,那么你需要使用 Spring-JDBC 模块。

Spring-ORM

Spring-ORM 是一个囊括了很多持久层技术(JPA,JDO,Hibernate,iBatis)的总括模块。对于这些技术中的每一个,Spring 都提供了集成类,这样每一种技术都能够在遵循 Spring 的配置原则下进行使用,并平稳地和 Spring 事务管理进行集成。
对于每一种技术,配置主要在于将一个 DataSource bean 注入到某种 SessionFactory 或者 EntityManagerFactory 等 bean 中。纯 JDBC 不需要这样的一个集成类(JdbcTemplate 除外),因为 JDBC 仅依赖于一个 DataSource。
如果你计划使用一种 ORM 技术,比如 JPA 或者 Hibernate,那么你就不需要 Spring-JDBC 模块了,你需要的是这个 Spring-ORM 模块。

Spring-Data

Spring-Data 是一个提供了一个通用 API 来定义如何以一个更通用的方式访问数据(DAO + 注解)的总括模块,包括 SQL 和 NOSQL 数据源。
初步设想是提供一种技术,开发者可以以一种技术无关的方式为 DAO 和实体类写接口,仅仅基于配置(在 DAO 和实体类加注解 + spring 配置,或者 xml 配置),就可以决定实现技术是 JPA(SQL) 还是 redis,hadoop 等等。
如果你遵循了 spring 为方法查找定义的命名规范,大多数简单的场景下你甚至都不需要为查找方法提供查询字符串。其他少数情况下,你需要在查找方法的注解里提供查询字符串。
在应用上下文被加载后,spring 为 DAO 接口提供代理,它包含有数据访问技术相关的所有样板代码,并调用配置的查询。
Spring-Data 专注于非 SQL 技术,但仍为 JPA(唯一的 SQL 技术,其他诸如 Hibernate 等没有)提供了一个模块。

该选择哪个

了解了这些,现在你可以决定选用哪一个了。好消息是你不需要为底层技术做一个明确的最终选择。这正是 Spring 主旨所在:作为一名开发人员,在你写代码的时候更多专注于业务逻辑,如果你把业务处理好了,改变底层技术只是一些实现或者配置细节的工作。

  1. 使用 POJO 类为实体定义一个数据模型,并使用 get 和 set 方法来表示实体的属性以及和其他实体的关系。你当然需要根据底层技术的需要为实体类及其方法加注解,但是现在,作为入门 POJO 足够了。现在你只需要专注于业务需求。
  2. 为你的 DAO 定义接口。一个 DAO 刚好包含一个实体,但你也无需为每个实体都对应一个 DAO,因为通过关联关系你可以加载到额外的实体。遵循严格的命名约定定义查找器方法。
  3. 藉此,其他人就可以模仿你的 DAO 开始业务层的工作了。
  4. 你可以学习不同的持久层技术(sql,no-sql)来寻找最适合你需要的那个,并最终选择一个。藉此,为你的实体注解并实现 DAO(或者如果你选择使用 Spring-Data 你可以让 Spring 来实现 DAO)。
  5. 如果你的业务需求进化,而你的数据访问技术对此的支持并不充分(比方说,一开始你使用了 JDBC,只有寥寥几个实体,但是现在需要一个更加丰富的数据模型,而 JPA 是一个比较好的选择),那么你需要修改你的 DAO 实现,在你的实体上添加一些注解并修改 Spring 配置(添加一个 EntityManagerFactory 定义)。但这些也只是持久层的改动,对于业务逻辑层的代码不应该受到到因此改动的影响。

注意:事务管理

Spring 为事务管理提供了一个 API。如果你的数据访问计划使用 spring,那么你也应该用 spring 来做事务管理,因为它们整合在一起确实很好。对于每一个 spring 支持的数据访问技术,都有一个为本地事务匹配的事务管理器,或者如果你需要分布式事务你也可以选择 JTA。它们都实现了同样的 API,因此持久层技术的选择只是一个随意调整的配置的事情,它的更改不会影响到业务代码。
原文链接:http://stackoverflow.com/questions/24990400/spring-dao-vs-spring-orm-vs-spring-jdbc

本文转自http://blog.csdn.net/defonds/article/details/47445915 感谢作者

以上是关于Spring 04 -SpringAOP开发的主要内容,如果未能解决你的问题,请参考以下文章

04. Spring注解详解

Spring 学习04

Spring-04.Spring的常用注解

黑马_13 Spring Boot:04.spring boot 配置文件

spring框架总结(04)----介绍的是Spring中的JDBC模板

Spring框架学习04