Spring基础——AOP

AOP是什么

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用”横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP核心概念

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

对连接点进行拦截的定义

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、返回、异常、正常返回、环绕通知五类

6、目标对象

代理的目标对象

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

Spring AOP 和 Struts2 拦截器区别?

struts2的拦截器是用来过滤页面请求,页面请求到达action前会被过滤器拦截, 而AOP实际是GOF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,主要是用来解决OOP和过程方法不能够很好解决的横切(crosscut)问题。

简单来说:

Spring AOP:只能拦截Spring管理Bean的访问(业务层Service)
Struts2 :拦截以 .action结尾的url,拦截Action的访问。

Spring AOP 通知示例(基于XML)

这里写图片描述

ProductInfoService接口包含了方法browse,模拟用户浏览网页。

ProductInfoServiceImpl是对ProductInfoService接口的实现。

AllLogAdvice是日志通知类,里面有具体的方法实现。

ProductInfoService:

package com.shw.service;

public interface ProductInfoService {
    public void browse(String userName,String productName);
}

ProductInfoServiceImpl:

package com.shw.service.impl;

import com.shw.service.ProductInfoService;

public class ProductInfoServiceImpl implements ProductInfoService {

    @Override
    public void browse(String userName, String productName) {
        System.out.println("执行业务方法browse");

        int i=100000000;
        while(i>0){
            i--;
        }   
    }
}
package com.shw.aop;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class AllLogAdvice {

    // 此方法将作为前置通知
    public void myBeforeAdvice(JoinPoint jionpoint) {
        // 获取方法参数
        List<Object> args = Arrays.asList(jionpoint.getArgs());
        // 日志格式字符串
        String logInfoText = "前置通知:"
                + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                        .format(new Date()) + " " + args.get(0).toString()
                + " 浏览商品 " + args.get(1).toString();
        // 将日志信息输出到控制台
        System.out.println(logInfoText);

    }

    // 此方法将作为返回通知
    public void myAfterReturnAdvice(JoinPoint jionpoint) {
        // 获取方法参数
        List<Object> args = Arrays.asList(jionpoint.getArgs());
        // 日志格式字符串
        String logInfoText = "返回通知:"
                + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                        .format(new Date()) + " " + args.get(0).toString()
                + " 浏览商品 " + args.get(1).toString();
        // 将日志信息输出到控制台
        System.out.println(logInfoText);
    }

    // 此方法将作为异常通知
    public void myThrowingAdvice(JoinPoint jionpoint, Exception e) {
        // 获取被调用的类名
        String targetClassName = jionpoint.getTarget().getClass().getName();
        // 获取被调用的方法名
        String targetMethodName = jionpoint.getSignature().getName();
        // 日志格式字符串
        String logInfoText = "异常通知:执行" + targetClassName + "类的"
                + targetMethodName + "方法时发生异常";
        // 将日志信息输出到控制台
        System.out.println(logInfoText);
    }

    // 此方法将作为环绕通知
    public void myAroundAdvice(ProceedingJoinPoint jionpoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        jionpoint.proceed();//继续执行业务
        long endTime = System.currentTimeMillis();
        // 获取被调用的方法名
        String targetMethodName = jionpoint.getSignature().getName();
        // 日志格式字符串
        String logInfoText = "环绕通知:" + targetMethodName + "方法调用前时间" + beginTime
                + "毫秒," + "调用后时间" + endTime + "毫秒";
        // 将日志信息输出到控制台
        System.out.println(logInfoText);
    }
}
<?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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">

    <!-- 配置业务类的Bean -->
    <bean id="productInfoService" class="com.shw.service.impl.ProductInfoServiceImpl">
    </bean>

    <!-- 配置日志通知类(切面)的Bean -->
    <bean id="allLogAdvice" class="com.shw.aop.AllLogAdvice"></bean>

    <!-- 配置aop -->
    <aop:config>

        <!-- 配置日志切面 -->
        <aop:aspect id="logaop" ref="allLogAdvice">

            <!-- 定义切入点,切入点采用正则表达式execution(* com.shw.service.ProductInfoService.*(..)),含义是对com.shw.service.ProductInfoService中的所有方法,都进行拦截 -->
            <aop:pointcut id="logpointcut"
                expression="execution(* com.shw.service.ProductInfoService.*(..))" />

            <!--  将AllLogAdvice日志通知类中的myBeforeAdvice方法指定为前置通知 --> 
            <aop:before method="myBeforeAdvice" pointcut-ref="logpointcut" />

            <!-- 将AllLogAdvice日志通知类中的myAfterReturnAdvice方法指定为返回通知   -->
            <aop:after-returning method="myAfterReturnAdvice"
                pointcut-ref="logpointcut" />

            <!-- 将AllLogAdvice日志通知类中的myThrowingAdvice方法指定为异常通知  --> 
            <aop:after-throwing method="myThrowingAdvice"
                pointcut-ref="logpointcut" throwing="e" />

            <!-- 将AllLogAdvice日志通知类中的myAroundAdvice方法指定为环绕通知 -->
             <aop:around method="myAroundAdvice" pointcut-ref="logpointcut" />
        </aop:aspect>
    </aop:config>
</beans>

测试类

package com.shw;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.shw.service.ProductInfoService;

public class TestAOP {
    public static void main(String[] args) {
        // 加载applicationContext.xml配置
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        // 获取配置中的实例
        ProductInfoService productInfoService = (ProductInfoService) ctx
                .getBean("productInfoService");
        // 调用方法
        productInfoService.browse("zhangsan", "理光THETA M15");
    }
}

输出:

前置通知:2018-04-01 15:06:10 zhangsan 浏览商品 理光THETA M15
执行业务方法browse
环绕通知:browse方法调用前时间1522566370595毫秒,调用后时间1522566370601毫秒
返回通知:2018-04-01 15:06:10 zhangsan 浏览商品 理光THETA M15

Spring AOP 通知示例(基于@AspectJ注解)

为了防止XML配置文件过于庞大,一个好的办法就是直接在类上加注解,让Spring容器自动配置Bean以及切面切入点等信息。

ProductInfoServiceImpl类上加注解,Spring容器就会自动创建该实例。

@Component("productInfoService")
public class ProductInfoServiceImpl implements ProductInfoService {
}
package com.shw.aop;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

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

@Aspect
@Component
public class AllLogAdvice {
    /**
     *  使用@Pointcut注解定义一个切入点,切入点的名字为allMethod(),
     *  切入点的正则表达式execution(* com.shw.service.ProductInfoService*(..))
     *  含义是对com.shw.service.ProductInfoService中的所有方法,都进行拦截
     **/
    @Pointcut("execution(* com.shw.service.ProductInfoService.*(..))")
    private void allMethod(){}//定义切入点名字

    // 此注解声明myBeforeAdvice为前置通知
    @Before("allMethod()")
    public void myBeforeAdvice(JoinPoint jionpoint) {
        ...

    }


    // 此注解声明myAfterReturnAdvice方法为返回通知
    @AfterReturning("allMethod()")
    public void myAfterReturnAdvice(JoinPoint jionpoint) {
        ...
    }

    // 此注解声明方法myThrowingAdvice为异常通知
    @AfterThrowing(pointcut="allMethod()",throwing="e")
    public void myThrowingAdvice(JoinPoint jionpoint, Exception e) {
        ...
    }

    // 此注解声明方法myAroundAdvice为环绕通知   
    @Around("allMethod()")
    public void myAroundAdvice(ProceedingJoinPoint jionpoint) throws Throwable {
        ...
    }
}

applicationContext配置文件

<?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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!-- 配置自动扫描的包,扫描bean -->
    <context:component-scan base-package="com.shw"></context:component-scan>

    <!-- 开启基于@AspectJ切面的注解处理器 -->
    <aop:aspectj-autoproxy />

</beans>

最终执行结果一样。(除了时间…)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOPSpring框架中的一个重要模块,它提供了面向切面编程(AOP)的支持。AOP是一种编程思想,它可以在不改变原有代码的情况下,通过在程序运行时动态地将代码“织入”到现有代码中,从而实现对原有代码的增强。 Spring AOP提供了基于注解的AOP实现,使得开发者可以通过注解的方式来定义切面、切点和通知等相关内容,从而简化了AOP的使用。 下面是一个基于注解的AOP实现的例子: 1. 定义切面类 ```java @Aspect @Component public class LogAspect { @Pointcut("@annotation(Log)") public void logPointcut() {} @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { // 前置通知 System.out.println("执行方法:" + joinPoint.getSignature().getName()); } @AfterReturning("logPointcut()") public void afterLog(JoinPoint joinPoint) { // 后置通知 System.out.println("方法执行完成:" + joinPoint.getSignature().getName()); } @AfterThrowing(pointcut = "logPointcut()", throwing = "ex") public void afterThrowingLog(JoinPoint joinPoint, Exception ex) { // 异常通知 System.out.println("方法执行异常:" + joinPoint.getSignature().getName() + ",异常信息:" + ex.getMessage()); } } ``` 2. 定义业务逻辑类 ```java @Service public class UserService { @Log public void addUser(User user) { // 添加用户 System.out.println("添加用户:" + user.getName()); } @Log public void deleteUser(String userId) { // 删除用户 System.out.println("删除用户:" + userId); throw new RuntimeException("删除用户异常"); } } ``` 3. 在配置文件中开启AOP ```xml <aop:aspectj-autoproxy/> <context:component-scan base-package="com.example"/> ``` 在这个例子中,我们定义了一个切面类LogAspect,其中通过@Aspect注解定义了一个切面,通过@Pointcut注解定义了一个切点,通过@Before、@AfterReturning和@AfterThrowing注解分别定义了前置通知、后置通知和异常通知。 在业务逻辑类中,我们通过@Log注解标注了需要增强的方法。 最后,在配置文件中,我们通过<aop:aspectj-autoproxy/>开启了AOP功能,并通过<context:component-scan>扫描了指定包下的所有组件。 这样,当我们调用UserService中的方法时,就会触发LogAspect中定义的通知,从而实现对原有代码的增强。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值