Spring学习路之初级AOP模块

以下内容集成到了GitHub项目中,有兴趣的朋友可以看一下GitHub

https://github.com/HeShuai-GitHub/springDemo.git

随手记

ideaj中不可以建立和java关键字名字等同的包名

一、spring依赖引入(Maven)

<!--spring依赖 start-->

<!--这个jar文件包含Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
    <!--    这个jar 文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean 以及进行Inversion ofControl / Dependency Injection(IoC/DI)
        操作相关的所有类。如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了。-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
    <!--    这个jar 文件为Spring 核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,
        JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <!--支持缓存Cache(ehcache)、JCA、JMX、 邮件服务(Java Mail、COS Mail)、任务计划Scheduling(Timer、Quartz)方面的类。-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <!--模块提供了一个强大的表达式语言,用于在运行时查询和处理对象图。该语言支持设置和获取属性值;属性赋值,
        方法调用,访问数组的内容,收集和索引器,逻辑和算术运算,命名变量,并从Spring的IOC容器的名字对象检索,
        它也支持列表选择和投影以及常见的列表聚合。-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>

        <!--spring jdbc依赖,这个jar 文件包含对Spring 对JDBC 数据访问进行封装的所有类。-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <!--依赖数据库的jar包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.26</version>
        </dependency>

        <!--AOP引用 start-->
        <!--这个jar 文件包含在应用中使用Spring 的AOP 特性时所需的类和源码级元数据支持。-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.1</version>
        </dependency>
        <!--提供对AspectJ的支持,以便可以方便的将面向方面的功能集成进IDE中-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <!--AOP引用 end-->

        <!--spring依赖 end-->

二、AOP前言——动态代理

在此感谢只是肿态度 的博客对我的帮助,需要详细了解动态代理可以通过传送门查看

代理模式是为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
个人理解,代理模式其实就是一个对公共业务封装的类,对其他类的包装调用,举个现实中的例子

好比如我们去找房子住,我们可以选择自己找,也可以选择中介帮我们找,如果每个人都自己找,因为对房子资源不了解,费时费力,但是通过中介找房子,因为中介就是做这个的,他对房产资源很了解,很快就找到了房子。这样就节省了我们的时间(忽略掉中介费的问题)

在代理模式中的委托类就是找房子的我们,我们去把这件事委托给房产中介,也就是代理类。而代理类为了能和我们自己执行的预期结果一致,就调用我们,代理类在调用委托类的前后做了消息的过滤、传递及处理等操作。
java中的代理模式分为静态代理和动态代理,这个很好理解,静态代理就是固定的一种代理模式,而动态代理就是根据运行时的数据而动态生成的一种代理模式

1.静态代理

最简单的静态代理就是 创建一个接口,在接口中声明一个方法,再创建两个实现该接口的实现类,这两个类中一个是代理类,一个委托类(即被代理类),代理类中包含委托类的引用成员属性,并在代理类的实现方法中调用委托类的该方法,在调用该方法前后做一些操作。
例子:(例子基本和只是肿态度 的博客相同,只是加了一个我个人的理解)

public interface HelloInterface {

    public void sayHello();

}
/**
 * @program: springDemo
 * @description: 具体业务实现类,对HelloInterface的实现,在这里也叫委托类
 * @author: hs
 * @create: 2020-08-14 15:39
 **/
public class HelloImpl implements HelloInterface{

    public void sayHello() {
        System.out.println("HelloImpl:HELLO");
    }
}
/**
 * @program: springDemo
 * @description: 静态代理实现类,也就是代理类
 * @author: hs
 * @create: 2020-08-14 15:41
 **/
public class HelloProxy implements HelloInterface{

    private HelloInterface hello=new HelloImpl();

    public void sayHello() {
        System.out.println("Before");
        hello.sayHello();
        System.out.println("After");
    }
}
public class ProxyMain {

    /**
     * 实际业务代码在HelloImpl中,它是委托类,也是实际应该执行的代码,HelloProxy是代理类,它的作用就是代替HelloImpl去做某件事,但是
     * 为了最后结果和预期结果是一致的,所以以间接调用了委托类的形式来完成这件事情
     *
     * @param args
     */
    public static void main(String[] args) {
        HelloInterface hello=new HelloProxy();
        hello.sayHello();
    }

}

大概例子如上所示,使用静态代理,因为代理对象真实存在,所以代理方式固定,这也代表了每需要一个代理都需要创建一个相关的代理类,这其实是很不方便的,而动态代理就是为了减少这个问题

2.动态代理

动态代理,顾名思义就是在运行过程中动态生成的代理方式,目前我所知的动态代理有两种方式:

一是JDK动态代理,这个是通过Reflex包下的Proxy类和InvocationHandler来生成的代理对象,是通过实现委托类的接口来实现代理,也就是对接口做代理;
二是cglib,cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。
他们的区别:

  1. JDk动态代理只可以对实现过接口的类进行动态代理,而cglib可以对除了被final修饰的类之外任何类代理
    2.JDK动态代理是对接口的代理,也就是只能代理接口中声明的方法;cglib是对类的代理,他可以代理委托类中的任意方法

例子(因为目前只对JDK动态代理熟悉,所以只介绍这种代理方式):
保留静态代理中的HelloImpl 和HelloInterface,创建一个实现InvocationHandler接口的ProxyHandler动态代理控制器


/**
 * @program: springDemo
 * @description: 动态代理控制器,在委托类调用方法前后 输出一条语句
 * @author: hs
 * @create: 2020-08-14 19:46
 **/
public class ProxyHandler implements InvocationHandler {

    private Object object;

    public ProxyHandler(Object object) {
        this.object = object;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "+method.getName());
        Object object=method.invoke(this.object,args);
        System.out.println("After invoke "+method.getName());
        return object;
    }
}

再创建两个相似于HelloInterface接口,来对比和静态代理的区别

public interface ByeInterface {

    public void sayBye();

}
public class ByeImpl implements ByeInterface {

    public void sayBye() {
        System.out.println("ByeImpl:Bye");
    }
}

动态代理Main执行类


 * @program: springDemo
 * @description: 动态代理 main方法
 * JDK动态代理,动过Proxy类来实现,利用反射机制在运行时创建代理类。
 * @author: hs
 * @create: 2020-08-14 19:56
 **/
public class DynamicMain {

    public static void main(String[] args) {
        HelloInterface hello=new HelloImpl();
        ProxyHandler handler=new ProxyHandler(hello);
//      通过Proxy类的静态方法newProxyInstance返回一个接口的代理实例
        HelloInterface helloInterface=(HelloInterface)Proxy.newProxyInstance(hello.getClass().getClassLoader(),hello.getClass().getInterfaces(),handler);
        helloInterface.sayHello();
        System.out.println("**********************");
        ByeInterface bye=new ByeImpl();
        //可使用同一个动态代理控制器来满足需要同样规则的委托类
        handler=new ProxyHandler(bye);
        ByeInterface byeInterface=(ByeInterface)Proxy.newProxyInstance(bye.getClass().getClassLoader(),bye.getClass().getInterfaces(),handler);
        byeInterface.sayBye();


    }

三、AOP

AOP(Aspect-OrientedProgramming)是spring的两大特性之一,即面向切面编程。spring aop的本质也就是动态代理,他是动态代理的一个应用,简单的称之为通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作
动态代理有两种实现方式,spring aop是混合使用了两种方式,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。它的默认策略是如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理。

1. AOP相关概念

1.1 AOP是横向抽机制,这个和OOP的纵向集成机制不同

1.2 横切关注点:从每个方法中抽取出来的非业务代码

1.3 切面(Aspect):封装横切关注点的类,每个关注点都体现一个通知方法,动态代理概念中的代理类;

1.4 通知(Advice):通知和横切关注点是一个东西,只是在不同的地方称呼不同,通知分为前置通知、后置通知、返回通知、异常通知、环绕通知五种

1.5 目标(Target):目标对象,也就是被通知的目标,动态代理概念中的委托类;

1.6 代理(Proxy):向目标对象应用通知之后生成的代理对象,这个等同于JDK动态代理通过Proxy.newProxyInstance()生成的代理对象

1.7 连接点(Joinpoint):横向切入点在程序中的具体实现,简单点理解就是在动态代理中本应放在实现类中的非业务代码,被抽取出来的位置就叫做连接点,同时它也是通知所需要通知的位置;

1.8 切入点(Pointcut):就是定位连接点方式,每个类中包含多个连接点,每个连接点是客观存在的事物。而切入点相当于更大范围的定位,也就是说当一个切面想要应用于某个类时需要用到切入点来定位出来,切入点相当于一个表达式、一个条件,表示出切面在哪里、在什么时候切入

2 Aspectj

Aspectj是spring aop中最好的实现方式,他通过注解的方式进行实现。

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

    <!--开启spring 组件扫描,并且隐式地注册了<context:annotation-config/>,开启spring的注解配置-->
    <context:component-scan base-package="com.spring.aop.aspectj"></context:component-scan>
    
    <!--开启aop aspectj的注解方式-->
    <aop:aspectj-autoproxy/>

</beans>

1.2 创建一个接口,两个实现类进行测试

public interface HelloInterface {

    public void sayHello();

}
/**
 * @program: springDemo
 * @description: 具体业务实现类,对HelloInterface的实现
 * @author: hs
 * @create: 2020-08-14 15:39
 **/
@Component
public class HelloImpl implements HelloInterface{

    public void sayHello() {
        /**
         * System.out.println("Before method")
         * 在Aspect中的前置通知中所执行的这个语句,本来应该是在现在这个位置执行的,在这里它称之为横向关注点
         */
        System.out.println("HelloImpl:HELLO");
    }
}
@Component
public class Bye {
    public void sayBye() {
        System.out.println("ByeImpl:Bye");
    }
}

1.3 创建一个切面类,来进行aop通知管理

/**
 * @program: springDemo
 * @description: 切面,aspectj是对spring aop的最好实现,是以注解的方式进行实现的
 * spring 原生aop是以xml的方式进行实现的
 * 切面:封装横切关注点的类,每个关注点都体现一个通知方法,动态代理概念中的代理类;
 * @author: hs
 * @create: 2020-08-14 22:43
 **/
@Component //使该类被spring ioc容器加载管理
@org.aspectj.lang.annotation.Aspect //指定被标记类为切面,即动态代理中的代理类
public class Aspect {


    /**
     * @Around 标记该方法为环绕通知,注解内value参数为切入点表达式
     * 环绕通知基本等同于动态代理中的实现InvocationHandler的invoke方法,所以应用较少
     * @ProceedingJoinPoint 可以用这个参数来调用目标对象
     * @return 一定要有返回值,这个就相当于动态代理的返回值,如果缺少返回值,相当于调用过程没有完成
     */
    @Around("execution(* com.spring.aop.*.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint){
        try{
            //@Before 此位置的代码等同于前置通知的作用
            System.out.println("前置通知");
            Object result=joinPoint.proceed();
            //@AfterReturning 此位置的代码等同于返回通知的作用
            System.out.println("返回通知");
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //@AfterThrowing 此位置的代码等同于异常通知的作用
            System.out.println("异常通知");
        }finally {
            //@After 此位置的代码等同于后置通知的作用
            System.out.println("后置通知");
        }
        return null;
    }

    /**
     * @Before 标记该方法为前置通知,注解内value参数为切入点表达式
     * 这个是必须设置的,指定切入规则
     * 切入点表达式:
     *    *:可以表示public void(返回值和范围修饰符),某个包、某个类; 注:一个*表示一个包,不可以表示多个包
     *    ..:表示任意的形参形式
     * @JoinPoint 连接点,即切面切入后的具体位置,JoinPoint中包含目标对象的一些基本信息,包括参数,类名、方法名等
     */
    //@Before("execution(public void com.spring.aop.aspectj.HelloImpl.sayHello())")
    @Before("execution(* com.spring.*.*.*.*(..))")
    public void before(JoinPoint joinPoint){
        //获得连接点所在的参数列表
        Object[] objects=joinPoint.getArgs();
        // 声明的className,即全限定名
        String className=joinPoint.getSignature().getDeclaringTypeName();
        //方法名
        String methodName=joinPoint.getSignature().getName();
        System.out.println("Before "+className+":"+methodName+"   参数:"+ Arrays.toString(objects));
    }

    /**
     * @After 标记该方法为后置通知,作用于方法运行最后,通常用来关闭一些必要的资源
     * 不管目标对象是否发生异常都会执行后置通知,即动态代理中的finally语句块
     */
    @After("execution(* com.spring.aop.*.*.*(..))")
    public void after(){
        System.out.println("后置通知");
    }

    /**
     * @AfterReturning 标记该方法为返回通知,作用于方法正常返回之后,输出一些运行结果,
     * 若目标对象发生异常,将不执行返回通知
     * returning 声明一个返回值参数,需要在返回通知方法的形参中声明同名参数,以此来接受目标对象运行结果
     */
    @AfterReturning(value = "execution(* com.spring.aop.*.*.*(..))",returning = "returnM")
    public void afterReturning(Object returnM){
        System.out.println("返回通知,结果:"+returnM);
    }

    /**
     * @AfterThrowing 标记该方法为异常通知,作用于方法发生异常时,做一些对异常处理的操作
     * 相当于 动态代理中的catch语句块
     * throwing 声明一个异常参数,需要在异常通知的形参中声明同名参数,来接受目标对象在运行过程中发生的异常
     * 可以通过定义不同类型的异常类,如:Exception、PointNullException来指定异常通知需要处理的异常类型
     */
    @AfterThrowing(value = "execution(* com.spring.aop.*.*.*(..))",throwing = "message")
    public void afterThrowing(Exception message){
        System.out.println("异常通知,异常:"+message);
    }

}

1.4 main 运行测试类

/**
 * @program: springDemo
 * @description: aop aspectj 主main
 * spring aop的底层实现是使用了动态代理的设计模式,采用了JDK动态代理和cglib动态代理两种方式混合使用,当目标对象(委托类)
 * 有实现接口时,优先使用JDK动态代理,否则使用cglib动态代理
 * @author: hs
 * @create: 2020-08-15 09:29
 **/
public class AspectjMain {

    public static void main(String[] args) {
        //必须加载spring容器,才可以开启aspectj的功能
        ApplicationContext ap=new ClassPathXmlApplicationContext("aop/Aspectj-Context.xml");
        /**
         * 这里不可以获取HelloImpl这个类的bean,也不可以获取这个类型,因为在aop的运行中,已经将"helloImpl"重新包装
         * 成了一个代理类,而被spring ioc管理的也是这个代理类。当获得id为helloImpl的bean时获取到的是代理类,而不是HelloImpl
         * 这个类本身。
         * 因为这个代理类和HelloImpl类之间,在JDK动态代理中,是同时实现了同一个接口方法,所以不可以将代理类强转成HelloImpl类型,但是可以
         * 上转型成它们的接口HelloInterface
         */
        HelloInterface hello=ap.getBean("helloImpl",HelloInterface.class);
        hello.sayHello();
        /**
         * Bye未实现接口,默认使用cglib代理方式,以继承类的方式来实现动态打理
         */
        Bye bye=ap.getBean("bye",Bye.class);
        bye.sayBye();
    }

}
3.spring aop 原生实现切面方式

spring aop 方式就是使用xml文件的配置方式,相对于Aspectj的注解自动配置来说,使用起来确实要麻烦一些,但是一些概念,包括切面、切入点表达式、通知等,基本上和Aspect一致

直接上代码,以下是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 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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启spring 组件扫描,并且隐式地注册了<context:annotation-config/>,开启spring的注解配置-->
    <context:component-scan base-package="com.spring.aop.aspectj"></context:component-scan>

    <!--用原生方法创建spring aop切面-->
    <aop:config>
        <!--创建一个公共切入点表达式-->
        <aop:pointcut id="cut" expression="execution(* com.spring.aop.aspectj.*.*(..))"/>
        <!--创建一个切面,并创建前置通知-->
        <aop:aspect ref="aspectTwo">
            <aop:before method="before" pointcut-ref="cut"></aop:before>
        </aop:aspect>

    </aop:config>

</beans>

其他的和Aspectj测试所用的类基本一致,这里再创建一个main运行的测试类就可以了

public class PrototypeMain {

    public static void main(String[] args) {
        ApplicationContext ap=new ClassPathXmlApplicationContext("aop/Prototype-Context.xml");
        Bye bye=ap.getBean("bye",Bye.class);
        bye.sayBye();
    }

}

四、结语

自此,spring aop的一些概念已经学习到一个阶段了,接下来可能会学习spring对数据库的操作支持,包括jdbcTemplate和事务处理等,完整完成spring使用层面上的学习,以后有机会可能会在进行spring源码的学习的

再次感谢 学习过程中,博客园各个博主对我的帮助,就不一一列举了,非常感谢,我感觉有帮助的文章已经有引入到本章中,感谢

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值