spring|面向切面编程AOP

面向切面编程

Ioc使软件组件松耦合。AOP让你能够捕捉系统中 经常使用的功能,把它转化成组件。

AOP(Aspect Oriented Programming):面向切面编程(AOP是一种编程技术

切面就是:程序中,和核心业务逻辑没有关系的通用代码

AOP是对OOP的补充延伸。

AOP底层使用的是 动态代理来实现的

Spring的AOP使用的动态代理是:JDK动态代理+CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果是代理某个类,这个类没有实现接口,就会切换使用CGLIB。  也可通过一些配置使得spring只使用CGLIB。

老杜说的面向切面编程:将业务逻辑中通用的非业务逻辑代码单独提取出来(比如日志、安全、事务),形成一个横向的切面,把核心业务逻辑看出是纵向的,我们以横向 交叉的方式 应用到业务流程当中的过程====AOP  后面说得怪怪的。。。 (通用的非业务逻辑的代码==交叉业务

1.AOP介绍

一般一个系统中都会有一些系统服务,例如:日志、事务管理、安全等。
这些系统服务被称为:交叉业务

这些交叉业务几乎是通用,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全 这些都是要做的。

像事务:事务的代码永远是那三部分:开启事务,提交事务,(遇到异常)回滚事务

如果在每一个业务处理过程中,都掺杂这些交叉业务代码进去的话,会存在两方面问题:

*一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处

*二:程序员无法专注于核心业务代码的编写,在编写核心业务代码的同时还需处理这些交叉业务

使用 AOP(面向切面编程--一种技术) 可以轻松解决以上问题。

看下图, 理解AOP思想

用一句话总结AOP:将与核心业务无关的代码 独立地抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

AOP的优点

1.代码复用性增强

2.代码易维护

3.使开发者更关注业务逻辑。

2.AOP的七大术语

连接点 JoinPoint
        在程序的整个执行流中,可以织入切面的位置。方法的执行前后,异常抛出之后的位置。

切点PointCut
        在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)

通知Advice
        通知又叫增强,就是具体你要织入的代码
        通知包括:前置通知,后置通知,环绕通知,异常通知,最终通知

切面Aspect
        切点+通知就是切面

织入Weaving
        把通知应用到目标对象上的过程。

代理对象Proxy
        一个目标对象被织入通知后产生的新对象

目标对象Target
        被织入通知的对象

 

 

(白色菱形表示 方法) 

 连接点 是指哪些 位置 能够让我们 织入(切入)
切点 是指 已经切入的地方,用方法去切入了,切入的方法就是切点
(好像是指核心代码的方法??把核心代码的方法 织入 我们的切面??)
通知 是指 你添加的那些非核心业务的通用代码------增强的代码

3.切点表达式

切点表达式用来定义通知(Advice)往哪些方法上切入。

切点表达式的语法格式 :
execution( [ 访问控制权限修饰符 ] 返回值类型 [全限定类名] 方法名(形参列表) [异常] )

访问控制权限修饰符:
 可选项(可写可不写)、没写,就是4个权限都包括、写public就表示只包括公开的方法

返回值类型:
必填项;  * 表示返回值类型任意

全限定类名:
  可选项;  两个点“.." 代表当前包以及子包下的所有类;  省略时表示所有的类

方法名:
  必填项; * 表示所有方法;  set* 表示所有的set方法

形式参数列表:
 必填项; ()表示里面没有参数; (..)参数类型和个数任意的方法;(*)只有一个参数的方法;
(* , String) 第一个参数类型任意,第二个参数类型为 String

异常:
 可选项; 省略时表示任意异常类型

4.使用spring的AOP

Spring对AOP的实现共三种方式:
第一种:Spring框架结合AspectJ框架实现的AOP,基于注解方式
第二种:Spring框架结合AspectJ框架实现的AOP,基于XML方式

第三种:Spring框架自己实现的AOP,基于XML方式

实际开发中,都是Spring+AspectJ来实现AOP。所以重点学第一第二种。

什么是AspectJ?(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架使用了AspectJ)

4.1准备工作(注解形式)


使用Spring+AspectJ的AOP需引入的依赖如下:

    <!--spring context依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.0-M2</version>
    </dependency>
<!--spring-aspects-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.0-M2</version>
        </dependency>

Spring配置文件中添加context命名空间和aop命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

4.2实现步骤(注解形式)

书写目标类和目标方法:
 

package com.spring6.service;
@Service
public class UserService {//目标类
    public void login(){//目标方法
        System.out.println("系统正在进行身份认证");
    }
}

书写切面
package com.spring6.service

@Component                                   ------使得能扫描到注解
@Aspect //切面类是需要使用@Aspect注解进行标注的
public class LogAspect {//切面
    //切面=切点+通知
//    通知就是增强,就是要编写的具体代码
//    这里通知Advice以方法的形式出现。(因为方法中可以写代码)
//    @Before注解  标注的方法  就是一个前置通知                   
                                                 ~添before注解,与增强代码形成通知
    @Before("execution(* com.spring6.service.UserService.*(..))")
    public void beforeAdvice(){                                  |
        System.out.println("我是一个通知,我是一个增强代码");       > 增强的代码
    }                                                           _|
}

书写spring配置文件:

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        
<!-- 组件扫描-->
    <context:component-scan base-package="com.spring6.service"/>
<!-- 开启aspectj的自动代理-->
<!-- spring容器在扫描类时,查看该类上是否有@Aspect注解,如果有,则给这个类生成代理对象-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- proxy-target-class="true"表示强制使用CGLIB动态代理;
      其默认值是false,表示接口使用JDK动态代理,反之使用CGLIB-->
</beans>

书写测试文件:

public class SpringAOPtest {
    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }
}

5.通知类型

通知类型包括:

前置通知:@Before  目标方法执行之前的  通知

后置通知:@AfterReturning  目标方法执行之后  的通知        

环绕通知:@Around  目标方法之前  添加通知,同时目标方法执行之后 添加通知

异常通知:@AfterThrowing 发送异常之后 执行的通知

最终通知:@After 放在 finally语句块中的通知

//前置通知
@Before("execution(* com.spring6.service..*(..))")
public void beforeAdvice() {
    System.out.println("前置通知,我是一个增强代码");
}

测试代码:

    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }

测试结果:

//后置通知
@AfterReturning("execution(* com.spring6.service..*(..))")
public void afterReturningAdvice() {
    System.out.println("后置通知,我是一个增强代码");
}

测试代码:

    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }

测试结果:

//环绕通知(环绕是最大的通知,在前置通知之前,在后置通知之后)
    @Around("execution(* com.spring6.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint JoinPoint) throws Throwable {
//        前面的代码
        System.out.println("前环绕");
//        执行目标
        JoinPoint.proceed();

//        后面的代码
        System.out.println("后环绕");
    }

测试代码:同上

测试结果:

//最终通知(finally语句块中的通知)
@After("execution(* com.spring6.service..*(..))")
    public void afterAdvice(){
    System.out.println("最终通知,这是增强代码");
}

测试代码同上

测试结果:

6.切面的顺序

当对同一个核心业务的代码,我们有多个切面要织入时,如何区分各个切面的先后顺序??

使用@Order(阿拉伯数字) 来区分先后顺序

@Service
@Aspect
@Order(3)
public class SecurityAspect {//安全切面
//    通知+切点
    @Before("execution(* com.spring6.service..*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知:安全。。");
    }
}
@Component
@Aspect //切面类是需要使用@Aspect注解进行标注的
@Order(2)
public class LogAspect {//切面

7.通用切点

//前置通知
@Before("execution(* com.spring6.service..*(..))")
public void beforeAdvice() {
    System.out.println("前置通知,我是一个增强代码");
}


//后置通知
@AfterReturning("execution(* com.spring6.service..*(..))")
public void afterReturningAdvice() {
    System.out.println("后置通知,我是一个增强代码");
}
//最终通知(finally语句块中的通知)
@After("execution(* com.spring6.service..*(..))")
    public void afterAdvice(){
    System.out.println("最终通知,这是增强代码");
}

上面括号内的 切点表达式 全都一样, ===代码复用差,每个都写一遍==要修改的话全修==

解决办法:写个通用切点

//    定义通用的切点表达式
    @Pointcut("execution(* com.spring6.service..*(..))")
    public void 通用切点(){
//        这个方法只是一个标记,方法名随意,方法体中也不需要写任何代码
    }

使用通用切点:

//前置通知
@Before("通用切点()")
public void beforeAdvice() {
    System.out.println("前置通知,我是一个增强代码");
}


//后置通知
@AfterReturning("通用切点()")
public void afterReturningAdvice() {
    System.out.println("后置通知,我是一个增强代码");
}

8.连接点Joint Point

    //前置通知
    @Before("通用切点()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("前置通知,我是一个增强代码");
//        这个JoinPoint joinPoint,在spring容器中调用这个方法的时候会自动传过来
//        我们可以直接用。用JoinPoint joinPoint干什么?
//        Signature signature = joinPoint.getSignature(); 这样就能 获取目标方法的签名
//        通过方法的签名能获取到一个方法的具体信息
//        获取目标方法的方法名
        System.out.println("目标方法的方法名:"+joinPoint.getSignature().getName());
    }

9.Spring AOP 基于注解之全注解开发

@Configuration //代替spring.xml配置文件
@ComponentScan({"com.spring6.service"})//组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//启用AspectJ自动代理机制
public class Spring6Config {
}

测试代码:

@Test
public void testNoXML(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
    OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
    orderService.generate();
}

10.基于XML方式实现Spring AOP(了解)

package com.spring6.service;

public class UserService {//目标对象
    public void logout(){//目标方法
        System.out.println("系统正在退出,别急");
    }
}

增强的代码(通知): 

package com.spring6.service;

import org.aspectj.lang.ProceedingJoinPoint;

public class TimerAspect {
//    通知
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//        前环绕
        long begin = System.currentTimeMillis();
//        目标
        joinPoint.proceed();
//        后环绕
        long end = System.currentTimeMillis();
        System.out.println("方法耗时"+(end-begin));

    }
}

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--    纳入spring-ioc-->
    <bean id="userService" class="com.spring6.service.UserService"></bean>
    <bean id="timerAspect" class="com.spring6.service.TimerAspect"></bean>
<!--    aop配置-->
    <aop:config>
<!--   切点表达式-->
        <aop:pointcut id="mypointCut" expression="execution(* com.spring6.service..*(..))"/>
<!--    切面:通知+切点-->
        <aop:aspect ref="timerAspect">
            <aop:around method="aroundAdvice" pointcut-ref="mypointCut"/>
        </aop:aspect>
    </aop:config>
</beans>
@Test
public void testXML(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.logout();
}

11.AOP的实际案例:事务处理

项目中的事务控制是在所难免的。在一个业务流程中,可能需要多条DML语句 共同完成,为保证数据的安全,这多条DML语句要么同时成功,要么同时失败。这就需要添加事务控制的代码,例如以下伪代码:

伪代码:

 可以看出,这些业务类中的每一个业务方法都是需要控制事务的,而控制事务的代码又是固定的形式,都是:

这个控制事务的代码就是和业务逻辑 没关系的 “交叉业务”。
以上代码中可以看到这些交叉业务的代码没有得到复用,并且如果这些交叉业务的代码需要修改,那必然要修改多处,维护困难。
如何解决?----------采用AOP思想解决。
可以把以上控制事务的代码作为环绕通知,切入到目标类的方法当中。
以上展示怎么做:有两个业务类 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值