通过三种方式实现AOP代理:Java代码实现JDK代理和Cglib代理、XML配置实现AOP代理、以及注解实现AOP代理

AOP的两种代理方式,JDK代理和cglib代理 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

  • 前言
  • 一、AOP相关概念
    • 1.AOP代理相关概念
    • 2.AOP开发明确的事项
    • 3.以上要点总结
  • 二、基于原始代码实现代理
    • 1.新建Maven项目,使用原始的代码实现JDK代理方法
    • 2.新建Maven项目,使用原始代码实现Cglib代理方法
  • 三、基于XML配置方式实现代理
    • 1.使用XML方式实现代理
    • 2.XML方式配置说明
  • 四、基于注解配置方式实现代理
    • 1.使用注解方式实现代理
    • 2.注解配置AOP详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、AOP相关概念
    • 1.AOP代理相关概念
    • 2.AOP开发明确的事项
    • 3.以上要点总结
  • 二、基于原始代码实现代理
    • 1.新建Maven项目,使用原始的代码实现JDK代理方法
    • 2.新建Maven项目,使用原始代码实现Cglib代理方法
  • 三、基于XML配置方式实现代理
    • 1.使用XML方式实现代理
    • 2.XML方式配置说明
  • 四、基于注解配置方式实现代理
    • 1.使用注解方式实现代理
    • 2.注解配置AOP详解


前言

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

一、AOP相关概念

1.AOP代理相关概念

  • (1)AOP相关概念
    • Spring的AOP实现方式就是对动态代码进行封装,封装后,只需要对需要关注的代码部分编写,并通过配置的方式完成指定目标的方法增强
  • (2)AOP相关术语
    • Target(目标对象) :代理的目标对象
    • Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类
    • JoinPoint(连接点): 指的是那些被拦截到的点.在Spring中,这些点值得是方法,因为spring值支持方法类型的连接点(可以被增强的方法叫做连接点)
    • PointCut(切入点): 指的是我们要对哪些JoinPoint进行拦截的定义(真正被增强的叫做切入点)
    • Advice(通知/增强): 指是拦截到JoinPoint之后所要做的事情就是通知/增强
    • Aspect(切面): 切入点和通知/增强的结合(目标方法+增强=切面)
    • Weaving(织入): 指把增强应用到目标对象来创建新的代理对象的过程.spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入(将切点和增强结合的过程叫织入)

2.AOP开发明确的事项

  1. 需要编写的内容
    • 编写核心业务方法(目标类的目标方法)
    • 编写切面类,切面类中有通知(增强功能方法)
    • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
  2. AOP技术实现的内容
    • Spring框架监控切入点方法的执行.一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行.
  3. AOP底层使用哪种代理方式
    • 在Spring中, 框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式.

3.以上要点总结

  • AOP:面向切面编程

  • AOP底层实现:基于JDK动态代理和基于Cglib的动态代理

  • AOP的重点概念:

    • Pointcut(切入点): 被增强的方法
    • Advice(通知): 封装增强业务逻辑的方法
    • Aspect(切面): 切点+通知
    • Weaving(织入): 将切点与通知结合的过程
  • 开发明确事项:

    • 谁是切点(切点表达式配置)
    • 谁是通知(切面类中的增强方法)
    • 将切点和通知进行织入配置

1.练习程序不区分Service、Dao层,都放在一个package中。
2.本例中的JKD代理方法和Cglib代理方法分别在两个package中实现

二、基于原始代码实现代理

1.新建Maven项目,使用原始的代码实现JDK代理方法

  • JDK代理方法必须有目标接口
目标对象
目标接口
代理对象
  • 本例中使用4个类
    • TargetInterface(目标接口)
    • Target(目标对象)
    • Advice(代理对象)
    • ProxyTest(测试类)
  1. 创建一个TargetInterface接口,作为目标接口
public interface TargetInterface {
    void save();
}
  1. 创建一个Target类,实现TargetInterface,作为目标对象
public class Target implements TargetInterface{
    @Override
    public void save() {
        System.out.println("save running.......");
    }
}
  1. 创建一个Advice类,作为代理对象
public class Advice {
    public void before(){
        System.out.println("前置增强.....");
    }
    public void afterReturning(){
        System.out.println("后置增强......");
    }
}
  1. 创建一个ProxyTest测试类
public class ProxyTest {
    public static void main(String[] args) {

        //目标对象
        final Target target=new Target();

        //增强对象
        final Advice advice=new Advice();

        //newProxyInstance的返回值是动态生成的代理对象,是接口
        TargetInterface proxy=(TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),     //目标对象类加载器
                target.getClass().getInterfaces(),      //目标对象相同的接口字节码对象数组
                new InvocationHandler(){
                    //调用代理对象的任何方法,实质执行的都是invoke方法
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before();
                        Object invoke=method.invoke(target,args);     //执行目标方法
                        advice.afterReturning();
                        return invoke;
                    }
                }
        );

        //调用代理对象的方法
        proxy.save();
    }
}

(5)运行测试类,查看是否成功

2.新建Maven项目,使用原始代码实现Cglib代理方法

  • Cglib代理方法与JDK方法的区别是少了代理接口,通过父类代理
目标对象
代理对象
  • 本例中使用三个类,分别是
  • Advice(代理对象)
  • Target(目标对象)
  • ProxyTest(测试类)
  1. 导入依赖坐标
	<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.3</version>
  	</dependency>
    <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
   	</dependency>
  1. 创建Target类作为目标对象
public class Target {
    public void save() {
        System.out.println("save running.......");
    }
}
  1. 创建Advice类作为代理对象
public class Advice {
    public void before(){
        System.out.println("前置增强.....");
    }
    public void afterReturning(){
        System.out.println("后置增强......");
    }
}
  1. 创建ProxyTest测试类
public class ProxyTest {
    public static void main(String[] args) {
        //目标对象
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();
        //返回值 动态生成的代理对象 基于Cglib
        //1.创建增强器
        Enhancer enhancer = new Enhancer();
        //2.设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3.设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //执行前置
                advice.before();
                //执行目标
                Object invoke = method.invoke(target, args);
                //执行后置
                advice.afterReturning();
                return invoke;
            }
        });
        //4.创建代理对象
        Target proxy = (Target) enhancer.create();
        proxy.save();
    }
}

(5)运行测试类,查看是否成功

三、基于XML配置方式实现代理

1.使用XML方式实现代理

  1. 导入pom依赖坐标:
	<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.3</version>
  	</dependency>
    <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
   	</dependency>
   	<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>3.0.7.RELEASE</version>
            <scope>test</scope>
 	</dependency>
	<dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
	</dependency>
  1. 创建一个TargeInterface接口,作为目标接口
public interface TargetInterface {
    void save();
}
  1. 创建一个Target类,实现TargetInterface接口,作为目标对象
public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("save running.......");
    }
}
  1. 创建一个MyAspect类,作为代理对象
public class MyAspect {
    public void before(){
        System.out.println("前置增强......");
    }
}
  1. 在resources目录下创建名为applicationContext.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
	    https://www.springframework.org/schema/beans/spring-beans.xsd
	    http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目标对象-->
    <bean id="target" class="com.lee.aop.Target"></bean>
    <!--切面对象-->
    <bean id="myAspect" class="com.lee.aop.MyAspect"></bean>
    <!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面:切点+通知-->
            <aop:before method="before" pointcut="execution(public void com.lee.aop.Target.save())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
  • a) 注意类的路径,根据实际需要修改,pointcut用于指定哪个类中的哪个方法.
  1. 在test目录下创建AppTest测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class })
public class AopTest {
    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}
  1. 运行测试类,查看是否成功.成功后,可以根据实际需求
  • 如果需要增强,则在配置文件中进行配置aop:config即可
  • 如果不需要增强,将aop:config标签注释或者删除即可

2.XML方式配置说明

  1. 切点表达式execution
切点表达式写法:
execution([修饰符] 返回值类型.包名.类名.方法名(参数))
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用星号*代表任意
  • 包名与类名之间一个点.代表当前包下的类,两个点…表示当钱包及其子包下的类
  • 参数列表可以使用两个点…表示任意个数,任意类型的参数列表

例如:

execution(public void com.lee.aop.Target.save())
execution(void com.lee.aop.Target.*(..))
execution(* com.lee.aop.*.*(..))
execution(* com.lee.aop..*.*(..))
execution(* *..*.*(..))
  1. 通知的类型
通知的配置语法:
<aop:通知类型 method="切面类中方法名" pointcut="切点表达式></aop:通知类型>
名称 标签 说明
前置通知 用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知 用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知 用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 用于配置异常通知,指定增强的方法在出现异常时执行
最终通知 用于配置最终通知,无论增强的方法是否有异常都会执行

该处使用的url网络请求的数据。

  1. 切点表达式的抽取
    当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式.
<?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
	    https://www.springframework.org/schema/beans/spring-beans.xsd
	    http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目标对象-->
    <bean id="target" class="com.lee.aop.Target"></bean>
    <!--切面对象-->
    <bean id="myAspect" class="com.lee.aop.MyAspect"></bean>
    <!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--抽取切点表达式-->
            <aop:pointcut id="myPointcut" expression="execution(public void com.lee.aop.Target.save())"/>
            <!--切面:切点+通知-->
            <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

四、基于注解配置方式实现代理

1.使用注解方式实现代理

  1. 创建接口TargetInterface作为代理接口
public interface TargetInterface {
    void save();
}
  1. 创建Target实现TargetInterface作为目标接口
@Component("target")
public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("save running.......");
    }
}
  1. 创建MyAspect作为切面类, 类中配置增强方法
@Component("myAspect")
@Aspect     //标注当前MyAspect是一个切面类
public class MyAspect {

    //配置前置增强
//    @Before("execution(* com.lee.anno.*.*(..))")
    public void before(){
        System.out.println("前置增强......");
    }

//    @AfterReturning("execution(* com.lee.anno.*.*(..))")
    public void afterReturning(){
        System.out.println("后置增强......");
    }

    @Around("execution(* com.lee.anno.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前置增强......");
        Object proceed=pjp.proceed();   //切点方法
        System.out.println("环绕后置增强......");
        return proceed;
    }
}
  1. 创建配置文件applicationContext-anno.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
	    https://www.springframework.org/schema/beans/spring-beans.xsd
	    http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.lee.anno"></context:component-scan>
    <aop:aspectj-autoproxy/>
</beans>
  1. 创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class })
public class AnnoTest {
    @Autowired
    private TargetInterface target;

    @Test
    public void test1() {
        target.save();
    }
}

运行查看测试结果

2.注解配置AOP详解

  1. 注解通知的类型
  • 通知配置语法 @通知注解(“切点表达式”)
名称 标签 说明
前置通知 @Before 用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知 @Around 用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 @AfterThrowing 用于配置异常通知,指定增强的方法在出现异常时执行
最终通知 @After 用于配置最终通知,无论增强的方法是否有异常都会执行
  1. 切点表达式的抽取
    使用@Pointcut抽取切点表达式,可以使用两种方式实现切点表达式的调用
@Component("myAspect")
@Aspect     //标注当前MyAspect是一个切面类
public class MyAspect {

    //配置前置增强
//    @Before("execution(* com.lee.anno.*.*(..))")
    public void before(){
        System.out.println("前置增强......");
    }

//    @AfterReturning("execution(* com.lee.anno.*.*(..))")
    public void afterReturning(){
        System.out.println("后置增强......");
    }

//    @Around("execution(* com.lee.anno.*.*(..))")
    @Around("pointcut()")				//切点表达式调用方式1,pointcut()
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前置增强......");
        Object proceed=pjp.proceed();   //切点方法
        System.out.println("环绕后置增强......");
        return proceed;
    }

    @After("MyAspect.pointcut()")		//切点表达式调用方式2,MyAspect.pointcut()
    public void after(){
        System.out.println("最终增强......");
    }

    @Pointcut("execution(* com.lee.anno.*.*(..))")
    public void pointcut(){};
}

你可能感兴趣的:(Java知识总结,java,aop,后端)