【Spring】Spring入门笔记2(1w字+)



IDE: IntelliJ IDEA 2020.2

零 l  写在前面


1、以初学者的视角记录笔记
2、课程链接:[链接]
3、资料链接:[笔记对应课程的配套资料],提取码:saq4
4、实操很重要!!!
5、本文主讲面向切面编程(AOP)
6、前文链接:[Spring入门笔记1]




一 l  引入


前一篇文章中我们有了IoC可以帮我们管理类实例。

又有新的问题出现了,

如果多个上层实例都调用一个下层实例(纵向的继承机制),

很容易导致牵一发而动全身,

换句话说就是耦合度太高了,

所以需要一种解耦的手段,

即 面向切面编程 AOP(Aspect Oriented Programming)这种横向的抽取机制

不使用继承,而是直接插入函数前或后。




二 l  AOP 介绍


(一)AOP 实现原理

aop底层将采用代理机制进行实现,有两种实现方法。
1、使用 jdk的Proxy 拦截来实现:有接口的实现类(默认)
2、使用 cglib字节码 子类增强来实现:有接口的实现类、无接口的实现类(更强大)

(二)AOP 术语

术语解释
target(目标类)目标类,需要被代理的类。
例如:UserService
Joinpoint(连接点)所谓连接点是指那些可能被拦截到的方法。
例如:所有的方法
PointCut(切入点)已经被增强的连接点。【添加完功能的代码】
例如:addUser()
advice通知/增强,增强代码。【增加的功能所在的源代码】
例如:after、before
Aspect(切面)pointcut 和 advice 的结合
Weaving(织入)advice + target = proxy的过程
proxy(代理对象)代理类

2.1




三 l  手动实现 AOP


通过下面的代码帮助理解AOP的原理(不使用Spring和AspectJ)。

(一)通用代码

接口:

public interface IUserService {
    public void addUser();
    public void updateUser();
    public void deleteUser();
    public int deleteUser(int id);
}

实现类target:

public class UserServiceImpl implements IUserService {
    @Override
    public void addUser() {
        System.out.println("添加用户。。。。");
    }

    @Override
    public void updateUser() {
        System.out.println("更新用户。。。。");
    }

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

    @Override
    public int deleteUser(int id) {
        System.out.println("通过id删除用户");
        return 1;
    }
}

切面类:

public class MyAspect {
    public void before(){
        System.out.println("开启事务...");
    }

    public void after(){
        System.out.println("提交事务...");
    }
}

调用的运行代码:

public class Lesson04 {
    @Test
    public void test1() throws Exception {
        IUserService userService = MyBeanFactory.createUserService();
        userService.deleteUser(10);
        userService.addUser();
        userService.updateUser();
    }
}

(二)JDK 动态代理

工厂类:

public class MyBeanFactory {
    public static IUserService createUserService(){
        // 1.创建目标对象target
        final IUserService userService = new UserServiceImpl();
        // 2.声明切面类对象
        final MyAspect2 aspect = new MyAspect2();
        // 3.创建JDK代理,拦截方法,下面是JDK代理函数的参数
        /*newProxyInstance(
                ClassLoader loader, 类加载器,写当前类
                Class<?>[] interfaces, 目标类接口,接口的方法会被拦截
                InvocationHandler h) 处理
                */
        IUserService seriviceProxy = (IUserService) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                userService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 开启事务
                        aspect.before();
                        
                        // method返回值是 目标类方法的返回值
                        Object retObj = method.invoke(userService,args);
                        
                        // 提交事务
                        aspect.after();
                        return retObj;
                    }
                }
        );
        return seriviceProxy;
    }
}

(三)cglib 增强字节码

没有接口,只有实现类。
采用字节码增强框架 cglib,在运行时创建目标类的子类,从而对目标类进行增强。

工厂类:

public class MyBeanFactory {
    public static StudentService createStudentService() {
        //1.创建目标对象target
        final StudentService studentService = new StudentService();
        //2.声明切面类对象
        final MyAspect2 aspect = new MyAspect2();
        //3.创建增强对象
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(studentService.getClass());
        //设置回调【拦截】
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                /**
                 * proxy:
                 * om.gyf.service.StudentService$$EnhancerByCGLIB$$fbb8ef26
                 * proxy代理对象是StudentService的子类
                 */
                aspect.before();
                
                // 放行方法
                // Object retObj = method.invoke(studentService,args);
                Object retObj = methodProxy.invokeSuper(proxy, args);  // 解耦,比上一行的方法好
                
                aspect.after();
                return retObj;
            }
        });
        //创建代理对象
        StudentService serviceProxy = (StudentService) enhancer.create();
        return serviceProxy;
    }
}





四 l  Spring AOP


第四部分主要介绍怎么调用Spring的AOP。

半自动是使用Spring容器配置代理,全自动是通过Spring容器使用AspectJ。

Spring按照Advice在target方法的joinpoint位置,分为5下面类。

类型解释
前置通知在目标方法执行前实施增强org.springframework.aop.MethodBeforeAdvice
后置通知在目标方法执行后实施增强org.springframework.aop.AfterReturningAdvice
环绕通知在目标方法执行前后实施增强org.aopalliance.intercept.MethodInterceptor
异常抛出通知在方法抛出异常后实施增强org.springframework.aop.ThrowsAdvice
引介通知在目标类中添加一些新的方法和属性org.springframework.aop.IntroductionInterceptor

(一)Spring编写AOP代理(半自动)

使用Spring的配置文件进行代理
1、之前的文章中已经导入了如下6个jar包
commons-logging-1.x.jarspring-aop-x.x.x.RELEASE.jarspring-beans-x.x.x.RELEASE.jarspring-context-x.x.x.RELEASE.jarspring-core-x.x.x.RELEASE.jarspring-expression-x.x.x.RELEASE.jar

2、现在还需要导入AOP联盟的jar包 >>>下载地址

3、解压aopalliance.zip后把aopalliance文件夹的aopalliance.jar放到之前自己的项目依赖文件夹中下

4、编写代码(不需要工厂类)

接口和实现类不变

切面类:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //拦截方法

        System.out.println("开启事务...");
        //放行
        Object retObj = mi.proceed();

        System.out.println("拦截.....");

        System.out.println("提交事务...");

        return retObj;
    }
}

配置文件:

<!--  配置UserService-->
<bean id="userService" class="com.gyf.service.UserServiceImpl"></bean>

<!--  配置切面类对象-->
<bean id="myAspect" class="com.gyf.aspect.MyAspect"></bean>

<!-- 配置代理对象
    默认情况下Spring的AOP生成的代理是JDK的Proxy实现的
-->
<bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 接口 :如果只是一个接口,就写Value,如果是多个接口就写List-->
    <property name="interfaces"  value="com.gyf.service.IUserService">
    </property>

    <!-- 目标对象 -->
    <property name="target" ref="userService"/>

    <!-- 切面类-->
    <property name="interceptorNames" value="myAspect"></property>

    <!-- 配置使用cglib生成-->
    <property name="optimize" value="true"></property>
</bean>

调用的运行代码:

public class Lesson07 {
    @Test
    public void test1() throws Exception {
        //获取Spring容器中代理对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans07.xml");
        IUserService userService = (IUserService) context.getBean("serviceProxy");
        userService.deleteUser();
    }
}

(二)Spring AOP(全自动)

1、下载aspectj.jar后放到项目依赖文件夹中。

如果报错找不到包,把aspectj-1.x.x.jar/files/lib中的aspectjrt.jaraspectjtools.jaraspectjweaver.jar放在依赖文件夹中。

2、编写代码(不需要工厂类)

接口、实现类、调用的运行代码不变

配置文件:第5、10、11行新增了命名空间

<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: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">

    <!--  配置UserService-->
    <bean id="userService" class="com.gyf.service.UserServiceImpl"></bean>

    <!--  配置切面类对象-->
    <bean id="myAspect" class="com.gyf.aspect.MyAspect"></bean>

    <!-- 全自动AOP配置
     1.在bean中配置aop约束
     2.配置aop:conifg内容,把切入点和通知结合

     proxy-target-class:使用cglib实现代理

     expression 表达式:*表示任意
                execution(*         com.gyf.service.*.   *       (..))
                          返回值    包名            类名 方法名  参数

     AOP:用于事务配置&日志记录
     -->
    <aop:config  proxy-target-class="true">
        <aop:pointcut id="myPointcut" expression="execution(* com.gyf.service.*.*(..))"/>

        <!-- 通知 关联 切入点-->
        <aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"></aop:advisor>
    </aop:config>
</beans>





五 l  AspectJ


第四部分主要介绍 AOP联盟接口AspectJ实现

AspectJ是一个基于Java语言的AOP框架,

Spring2.0以及上版本自带AspectJ,

建议使用AspectJ来更加灵活的开发AOP。(第四部分的全自动就是使用的AspectJ)

(一)AspectJ 通知类型

aop联盟定义通知类型,aop联盟的jar都是接口,必须要有实现类。

aspectj 通知类型,只定义类型名称,以及方法格式。

类型名称解释应用配置文件中的对应
before前置通知在方法执行前执行,如果通知抛出异常,阻止方法运行各种校验aop:before
around环绕通知方法执行前后分别执行,可以阻止方法的执行。必须手动执行目标方法可以做任何事情aop:around
afterReturning后置通知方法正常返回后执行,如果方法中抛出异常,通知无法执行。
必须在方法执行后才执行,所以可以获得方法的返回值。
常规数据处理aop:after-returning
afterThrowing抛出异常通知方法抛出异常后执行,如果方法没有抛出异常,无法执行包装异常信息aop:after-throwing
after最终通知方法执行完毕后执行,无论方法中是否出现异常清理现场aop:after

xml配置的执行顺序:(afterThrowing任意时刻都有可能触发)
before
->around的前置通知
->afterReturning
->around的后置通知
->after

(二)切入点表达式

1、execution()【重要】
作用:用于描述joinpoint
语法execution(修饰符 返回值 包.类.方法名(参数) throws异常)

位置类型示例是否可以省略
修饰符public【公共方法】
*【任意】
是(建议)
返回值void【返回没有值】
String【返回值字符串】
*【任意】
否(强制)
com.gyf.crm【固定包】
com.gyf.crm.*.service【crm包下面子包任意】 (例如:com.gyf.crm.staff.service)
com.gyf.crm…【crm包下面的所有子包(含自己)】
com.gyf.crm.*.service…【crm包下面任意子包,固定目录service,service目录任意包】
否(建议)
UserServiceImpl【指定类】
*Impl【以Impl结尾】
User*【以User开头】
*【任意】
否(建议)
方法名addUser【固定方法】
add*【以add开头】
*Do【以Do结尾】
*【任意】
否(强制)
参数()【无参】
(int)【一个整型】
(int ,int)【两个】
(…)【参数任意】
否(强制)
throws异常与java中相同是(建议)


2、其他表达式【了解】

表达式作用例子
within()匹配包或子包中的方法within(com.gyf.aop..*)
this()匹配实现接口的目标对象中的方法target(com.gyf.aop.user.UserDAO)
args()匹配参数格式符合标准的方法args(int,int)
bean(id)匹配指定的bean所有的方法bean('userServiceId')

(三)AspectJ的xml配置

1、导入 spring-aspects-x.x.x.RELEASE.jar 到依赖文件夹(经测试Spring5.2.8版本不需要)

2、编写代码

接口、实现类、调用的运行代码不变

切面类:

import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect{
    public void myBefore(JoinPoint jp){
        // 怎么获取方法名(都可以使用这个方法,包括around的ProceedingJoinPoint类)
        System.out.println("1.前置通知... 连接点方法名:" + jp.getSignature().getName());
    }

    public void myAfterReturning(Object retValue){
        System.out.println("3.后置通知...");
        // 怎么获取返回值(只有AfterReturning才能获取返回值,且要在xml中配置returning属性)
        System.out.println("返回值:" + retValue);
    }

	// ProceedingJoinPoint是手动放行的连接点;JoinPoint是自动放行的连接点
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("2.环绕通知-开启事务...");
        // 放行
        Object retObj = pjp.proceed();
        System.out.println("4.环绕通知-提交事务...");
        return retObj;
    }
    
    public void myAfterThrowing(JoinPoint jp, Throwable e){
        // 异常通知最好有方法名
        System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
    }
    
    public void myAfter(){
        System.out.println("最终通知...");
    }
}

配置文件:(命名空间与四-(二)相同)

<!-- 配置UserService -->
<bean id="userService" class="com.gyf.service.UserServiceImpl"></bean>

<!-- 配置切面对象 -->
<bean id="myAspect" class="com.gyf.aspect.MyAspect"></bean>

<!-- 配置 aop -->
<aop:config>
    <!-- aop:指定切面 -->
    <aop:aspect ref="myAspect3">
        <!-- 定义一个切入点 -->
        <aop:pointcut id="myPointcut" expression="execution(* com.gyf.service.UserServiceImpl.*(..))"/>
        
        <!-- 配置前置通知 before -->
        <aop:before method="myBefore" pointcut-ref="myPointcut" />
        <!-- 配置后置通知 after-returning -->
        <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
        <!-- 配置环绕通知 around -->
        <aop:around method="myAround" pointcut-ref="myPointcut"/>
        <!-- 配置异常通知 throwing="e" 值,是 调用方法 形参 的 "Throwable"类型 的 变量名 -->
        <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
        <!--配置最终通知:不管有没有异常,最终通知都会执行-->
        <aop:after method="myAfter" pointcut-ref="myPointcut"/>
        
    </aop:aspect>
</aop:config>





六 l  AspectJ的注解配置【重点】


因为比较常用,所以单独拎出来

1、声明使用注解

<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: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.gyf"/>
    <!-- 配置aop注解生效 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <!-- aop配置,切面类方法 -->
    <aop:config>
        <aop:aspect ref="myAspect"></aop:aspect>
    </aop:config>
</beans>


2、使用的注解类型可以查看Spring入门笔记1的第六部分

3、切面类中针对AOP的特殊注解
切面类:

@Component
@Aspect
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect{
    //声明一个公共的切入点
    @Pointcut("execution(* com.gyf.service.UserServiceImpl.*(..))")
    public void myPointcut(){}

    @Before("myPointcut()")
    public void myBefore(JoinPoint jp){
        System.out.println("1.前置通知... 连接点方法名:" + jp.getSignature().getName());
    }

    @AfterReturning(pointcut = "myPointcut()",returning = "retValue")
    public void myAfterReturning(Object retValue){
        System.out.println("3.后置通知...");
        System.out.println("返回值:" + retValue);
    }

	@Around("myPointcut()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("2.环绕通知-开启事务...");
        Object retObj = pjp.proceed();
        System.out.println("4.环绕通知-提交事务...");
        return retObj;
    }
    
    @AfterThrowing(pointcut = "myPointcut()",throwing = "e")
    public void myAfterThrowing(JoinPoint jp, Throwable e){
        System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
    }
    
    @After("myPointcut()")
    public void myAfter(){
        System.out.println("最终通知...");
    }
}
注解切入位置
@Aspect声明切面,修饰切面类,从而获得 通知。
@PointCut修饰方法 private void xxx(){} 之后通过“方法名”获得切入点引用
@Before前置
@AfterReturning后置
@Around环绕
@AfterThrowing抛出异常
@After最终





七 l  总结


(一)总结到目前为止使用过的包

包名作用类型位置
spring-core-x.x.x.RELEASE.jarSpring的核心工具类,其它jar包是建立这个包基础上的,都要用到这个包中的类。核心包spring-framework-x.x.x.RELEASE/libs
spring-beans-x.x.x.RELEASE.jar配置、创建、管理Bean,负责IoC、DISpring核心包spring-framework-x.x.x.RELEASE/libs
spring-context-x.x.x.RELEASE.jar提供在基础IoC上的扩展服务。Spring核心包spring-framework-x.x.x.RELEASE/libs
spring-expression-x.x.x.RELEASE.jar提供对Spring表达式语言的支持。Spring核心包spring-framework-x.x.x.RELEASE/libs
commons-logging-1.x.jar【第三方依赖】Spring核心包commons-logging-1.x
aopalliance.jar【第三方依赖】AOP联盟接口定义AOP核心包aopalliance
aspectj-1.x.x.jar【第三方依赖】AOP联盟接口定义的AspectJ实现AOP核心包
aspectjrt.jar/aspectjtools.jar/aspectjweaver.jar【第三方依赖】AOP联盟接口定义的AspectJ实现AOP核心包aspectj-1.x.x.jar/files/lib
spring-aop-5.2.8.RELEASE.jarSpring的AOP实现AOP核心包spring-framework-x.x.x.RELEASE/libs

(二)使用XML配置和注解配置,在执行顺序上的异同

配置类型有无返回值有无异常执行顺序
XML有/无->before【前置通知】
->around的前置通知
->执行原方法
->afterReturning【后置通知】
->around的后置通知
->after【最终通知】
注解有/无->around的前置通知
->before【前置通知】
->执行原方法
->around的后置通知
->after【最终通知】
->afterReturning【后置通知】

(三)对本文的总结

本文最需要关注的部分在第四五六部分

从Spring代理->AspectJ的XML自动代理->AspectJ的注解自动代理

从原理出发,逐渐简化代码,全文最重要也是最实用的在第六部分——使用注解配置AOP

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值