Spring AOP

Spring中的AOP

一.AOP简介

什么是AOP
Aspect Oriented Programing 面向切面编程,AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
SpringAOP就是在运行期通过动态代理的方式向目标类织入增强代码,为目标类中的方法添加额外的功能

二.AOP底层原理

AOP底层原理:就是代理机制
动态代理:
特点:在不修改源代码的基础上对目标中的方法进行增强

分类:
基于接口的动态代理
基于子类的动态代理

Spring的AOP代理:
​ JDK动态代理:被代理对象必须要实现接口,才能产生代理对象,如果没有接口将不能使用动态代理技术
​ CGLib动态代理:第三方代理技术,cglib代理可以对任何类生成代理,代理的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理

结论:Spring框架如果类实现了接口,就使用JDK动态代理生成代理对象,如果这个类没有实现任何接口,使用CGLIB生成代理对象

三.装饰器模式

装饰器模式:对象本身增强

代理模式:代理对象(代理过程)增强

3.1Info

package com.qf.decorator;

//抽象类:有抽象方法的类一定时抽象类,但是抽象类可以有普通方法和抽象方法
public abstract class Info {

    //自我介绍
    public abstract void info();

}

3.2PersonInfo

package com.qf.decorator;

//继承Info类,实现方法
public class PersonInfo extends Info {
    @Override
    public void info() {
        System.out.println("自我介绍...");
    }
}

3.3Decorator

package com.qf.decorator;

//装饰器,继承Info
public abstract class Decorator extends Info{

    private Info info;

    //传入被装饰对象
    public Decorator(Info info) {
        this.info = info;
    }

    @Override
    public void info() {
        info.info();
    }
}

3.4Singer

package com.qf.decorator;

public class Singer extends Decorator {

    public Singer(Info info) {
        super(info);
    }

    public void singing(){
        System.out.println("唱歌...");
    }

    @Override
    public void info() {
        super.info();
        singing();
    }
}

3.5Dancer

package com.qf.decorator;

public class Dancer extends Decorator {

    public Dancer(Info info) {
        super(info);
    }

    public void dancing(){
        System.out.println("跳舞...");
    }

    @Override
    public void info() {
        super.info();
        dancing();
    }
}

3.6Magic

package com.qf.decorator;

public class Magic extends Decorator {
    public Magic(Info info) {
        super(info);
    }

    public void magic(){
        System.out.println("变魔术");
    }

    @Override
    public void info() {
        super.info();
        magic();
    }
}

3.7测试

package com.qf.decorator;

public class Test {
    public static void main(String[] args) {

        //单独测试
//        Info personInfo = new PersonInfo();
//        personInfo.info();
//
//        Decorator singer = new Singer(personInfo);
//        singer.info();
//
//        Decorator dancer = new Dancer(personInfo);
//        dancer.info();

        //增强测试
        Info personInfo = new PersonInfo();
        Decorator singer = new Singer(personInfo);//第一次增强
        Decorator dancer = new Dancer(singer);//第二次增强
        Decorator magic1 = new Magic(dancer);//第三次增强
        Decorator magic2 = new Magic(magic1);//第四次增强
        magic2.info();
    }
}

四.代理模式

4.1静态代理

通过代理类的对象,为目标类的对象(原始类)添加功能

缺点:要实现目标接口中的所有方法,代理类的功能代码冗余,修改时,维护性差

4.1.1Rent
package com.qf.proxy.demo1;

//出租房子
public interface Rent {

    //出租
    public void rent();

    //还有一些其他方法
    //public void test();
}
4.1.2Owner
package com.qf.proxy.demo1;

//房东
public class Owner implements Rent{
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }
}
4.1.3OwnerProxy
package com.qf.proxy.demo1;

//中介:静态代理类
//代理:动态代理(JDK 和 Cglib)和静态代理
public class OwnerProxy implements Rent{

    private Owner owner;

    public OwnerProxy(Owner owner) {
        this.owner = owner;
    }

    public void publish(){
        System.out.println("发布租房信息");
    }

    public void seeHouse(){
        System.out.println("带租户看房");
    }

    @Override
    public void rent() {
        publish();
        owner.rent();
        seeHouse();
    }
}
4.1.4测试
package com.qf.proxy.demo1;

public class Test {
    public static void main(String[] args) {

        Owner owner = new Owner();
        owner.rent();

        System.out.println("-------------------------");

        OwnerProxy ownerProxy = new OwnerProxy(owner);
        ownerProxy.rent();
    }
}

4.2Jdk动态代理

4.2.1Rent
package com.qf.proxy.demo2;

//出租房子
public interface Rent {

    //出租
    public void rent();

    //还有一些其他方法
    public void test();
}
4.2.2Owner
package com.qf.proxy.demo2;

//房东
public class Owner implements Rent {
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }

    @Override
    public void test() {
        System.out.println("测试方法");
    }
}
4.2.3RentJdkProxy
package com.qf.proxy.demo2;

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

//JDK动态代理
public class RentJdkProxy implements InvocationHandler{

    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成代理对象
    public Rent getRent(){
        return (Rent) Proxy.newProxyInstance(
                rent.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),
                this);
    }

    //对接口中的方法进行扩展(增强),不需要实现接口中所有方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //声明方法的返回值
        Object result = null;
        //判断方法名,对其增强
        if("rent".equals(method.getName())){
            publish();
            result = method.invoke(rent,args);
            seeHouse();
        }else{
            result = method.invoke(rent,args);
        }

        return result;
    }

    public void publish(){
        System.out.println("发布租房信息");
    }

    public void seeHouse(){
        System.out.println("带租户看房");
    }

}
4.2.4测试
package com.qf.proxy.demo2;

public class Test {
    public static void main(String[] args) {

        Owner owner = new Owner();
        owner.rent();

        System.out.println("-------------------------");

        RentJdkProxy rentJdkProxy = new RentJdkProxy();
        rentJdkProxy.setRent(owner);

        Rent proxyRent = rentJdkProxy.getRent();//获取代理对象
        proxyRent.rent();

        proxyRent.test();
    }
}

五.AOP的术语

AOP的术语:
Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在spring中,这些点指的是方法,spring只支持方法类型的连接点。
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):可以在运行期为类动态地添加一些方法或Field。
Target(目标对象):代理的目标对象。
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。
Proxy(代理):一个类被AOP织入增强后,就产生一个代理类。
Aspect(切面):是切入点和通知(引介)的结合

如下图:

六.Spring中的XML配置AOP

4.1在pom.xml添加aop依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.20</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.9.1</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>compile</scope>
    </dependency>

</dependencies>

4.2创建UserService

package com.qf.service;

//目标对象target
public interface UserService {
    //未增强的方法叫做连接点JoinPoint
    //已增强的方法叫做切入点PointCut
    public void add();

    public void delete();
}

4.3创建UserServiceImpl

package com.qf.service.impl;

import com.qf.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("添加用户..");
        //int i = 1/0;
    }

    @Override
    public void delete() {
        System.out.println("删除用户..");
    }
}

4.4创建通知类

前置通知(before):目标方法运行之前调用
后置通知(after-returning):在目标方法运行之后调用 (如果出现异常不会调用)
环绕通知(around):在目标方法之前和之后都调用(ProceedingJoinPoint对象 -->> 调用proceed方法)
异常拦截通知(after-throwing):如果出现异常,就会调用
最终通知(after):在目标方法运行之后调用 (无论是否出现 异常都会调用)

package com.qf.advice;

import org.aspectj.lang.ProceedingJoinPoint;

//通知类,增强的代码(方法)Advice
public class MyAdvice {

    public void before(){
        System.out.println("前置通知,目标对象调用方法前执行");
    }

    public void after(){
        System.out.println("后置通知(最终通知),目标对象调用方法后执行,无论是否发生异常都执行");
    }

    public void after_returning(){
        System.out.println("后置通知,目标对象调用方法后执行,发生异常不执行");
    }

    public void after_throwing(){
        System.out.println("异常通知,发生异常执行");
    }

    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知,目标对象调用方法之前");
        joinPoint.proceed();
        System.out.println("环绕通知,目标对象调用方法之后");
    }

}

4.5创建applicationContext.xml(添加aop约束)

使用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"> <!-- bean definitions here -->

        <bean id="userService" class="com.qf.service.impl.UserServiceImpl"></bean>

        <!-- 通知 -->
        <bean id="myAdvice" class="com.qf.advice.MyAdvice"></bean>

        <!-- aop -->
        <!-- 默认使用JDK动态代理 -->
        <!-- proxy-target-class="true" 使用cglib -->
        <aop:config proxy-target-class="true">
                

<!-- 配置切入点 切入点表达式的写法:execution(表达式)
   public void com.abyg.service.UserServiceImpl.save() 
   void com.qf.service.UserServiceImpl.save()  其他修饰符无返回值的save空参方法
   * com.qf.service.UserServiceImpl.save()  有或者无返回值的save空参方法
   * com.qf.service.UserServiceImpl.*()  有或者无返回值的所有空参方法

   * com.qf.service.*ServiceImpl.*(..)  有或者无返回值的所有有参或者空参方法
   * com.qf.service..*ServiceImpl.*(..)  一般不用,service包下的子包和孙包以ServiceImpl结尾的类中的方法
-->
           
            		<!-- 切入点 -->
<!--                <aop:pointcut id="pc" expression="execution(public void com.qf.service.impl.UserServiceImpl.add())"/>-->
                <aop:pointcut id="pc" expression="execution(* com.qf.service.impl.*ServiceImpl.*(..))"/>
                <!-- 切面 -->
                <aop:aspect ref="myAdvice">
                        <!-- 配置前置通知对应的方法 -->
                        <aop:before method="before" pointcut-ref="pc"></aop:before>
                        <!-- 配置后置通知(最终通知)对应的方法 -->
                        <aop:after method="after" pointcut-ref="pc"></aop:after>
                        <!-- 配置后置通知对应的方法,发生异常不执行 -->
                        <aop:after-returning method="after_returning" pointcut-ref="pc"></aop:after-returning>
                        <!-- 配置异常通知对应的方法,发生异常执行 -->
                        <aop:after-throwing method="after_throwing" pointcut-ref="pc"></aop:after-throwing>
                        <!-- 配置环绕通知对应的方法 -->
                        <aop:around method="around" pointcut-ref="pc"></aop:around>
                </aop:aspect>
        </aop:config>

</beans>

4.6测试

package com.qf.test;

import com.qf.service.UserService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

    @Test
    public void testUserService(){

        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = (UserService)applicationContext.getBean("userService");

        userService.add();
        userService.delete();
    }

}

七.Spring中的注解配置AOP

使用注解方式配置AOP

7.1创建bean.xml文件配置注解方式

<?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 definitions here -->

<!-- 准备工作: 导入aop(约束)命名空间 -->
		<!-- 1.配置目标对象 -->
        <bean id="userService" class="com.qf.service.impl.UserServiceImpl"></bean>

        <!-- 2.配置通知对象 -->
        <bean id="myAdvice" class="com.qf.advice.MyAdvice"></bean>

        <!-- 3.开启使用注解完成织入 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

7.2在通知类上使用相关注解

@Aspect
//表示该类是一个通知类
//通知类,增强的代码(方法)Advice
public class MyAdvice {

    //自己设置一个切点,管理重复代码
	@Pointcut("execution(* com.qf.service.impl.*ServiceImpl.*(..))")
	public void pc(){}
    
	//前置通知
	//指定该方法是前置通知,并制定切入点
	@Before("MyAdvice.pc()")
    public void before(){
        System.out.println("前置通知,目标对象调用方法前执行");
    }

    //最终通知
	@After("execution(* com.qf.service.impl.*ServiceImpl.*(..))")
    public void after(){
        System.out.println("后置通知(最终通知),目标对象调用方法后执行,无论是否发生异常都执行");
    }

    //后置通知
	@AfterReturning("execution(* com.qf.service.impl.*ServiceImpl.*(..))")
    public void after_returning(){
        System.out.println("后置通知,目标对象调用方法后执行,发生异常不执行");
    }

    //异常通知
	@AfterThrowing("execution(* com.qf.service.impl.*ServiceImpl.*(..))")
    public void after_throwing(){
        System.out.println("异常通知,发生异常执行");
    }

    //环绕通知
	@Around("execution(* com.qf.service.impl.*ServiceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知,目标对象调用方法之前");
        joinPoint.proceed();
        System.out.println("环绕通知,目标对象调用方法之后");
    }

}

7.3测试

@Test
public void testUserService(){

    ClassPathXmlApplicationContext applicationContext =
        new ClassPathXmlApplicationContext("bean.xml");

    UserService userService = (UserService)applicationContext.getBean("userService");

    userService.add();
    userService.delete();
}
posted @ 2022-07-10 18:05  qtyanan  阅读(25)  评论(0编辑  收藏  举报