阅读 700

「2020最新」Spring最易学习教程 3— 代理设计模式 Spring AOP 动态代理原理

0 复习

  1. FactoryBean技术(用于创建复杂对象)

    复杂对象:底层不能直接通过new构造方法创建,通常需要若干步骤才能创建的对象。比如:Connection、SqlSession

    1. 编码

      implements FactoryBean

    2. 配置

      通过bean标签配置

  2. 配置文件

    1. import标签
    2. xsd使用规则
    3. 拆分jdbc.properties文件
  3. 概念总结

    IOC和DI

  4. Spring整合Struts2

    导入依赖:spring-web struts2-spring-plugin

    1. web.xml 在tomcat启动应用时,创建Spring工厂

      <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:applicationContext.xml</param-value>
      </context-param>

      <listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      复制代码
    2. Struts2从Spring工厂中获取Action

      applicationContext.xml

      bean标签配置action和service
      注意: action需要多例
      复制代码

      struts.xml

      <action name="路径" class="bean的id"></action>
      复制代码
  5. 注解开发

    Component(Controller、Service、Repository)替换bean标签

    Autowired替换property标签

Spring AOP

1 代理设计模式

1.1 解决的问题

image-20200602101013042
image-20200602101013042

要不要在业务层中定义额外功能?

业务调用者的角度:需要,业务方法中需要使用这些额外功能

软件设计者的角度:不需要,定义后会造成代码的频繁修改

矛盾的解决方案:代理模式

1.2 代理模式

image-20200602102246281
image-20200602102246281

矛盾:

房东不愿意提供额外功能(带看房,车接车送),因为麻烦

租客必须要使用这些额外功能

矛盾的解决方案:中介

中介代理了房东的出租房屋的方法,同时提供额外的功能。

房东无需亲自和租客打交道,节省时间。租客也可以享受完整的服务。

在程序中,Action(租客) 和 Serivce(房东) 的矛盾,也可以通过添加一个代理类解决。

1.3 静态代理

image-20200602103550535
image-20200602103550535

实战:

接口:

public interface UserService {
    public boolean login(String username,String password);
    public void removeUser(Integer id);
}
复制代码

原始实现类:

public class UserServiceImpl implements UserService {
    @Override
    public boolean login(String username, String password) {
        System.out.println("username = [" + username + "], password = [" + password + "]");
        return false;
    }

    @Override
    public void removeUser(Integer id) {
        System.out.println("id = [" + id + "]");
    }
}
复制代码

代理实现类:

public class UserServiceProxy implements UserService {
    private UserService userService = new UserServiceImpl();
    @Override
    public boolean login(String username, String password) {
        System.out.println("开启事务");
        boolean result = userService.login(username,password);
        System.out.println("结束事务");
        return result;
    }

    @Override
    public void removeUser(Integer id) {
        System.out.println("开启事务");
        userService.removeUser(id);
        System.out.println("结束事务");
    }
}
复制代码

使用者:

public class UserAction {
    private UserService userService = new UserServiceProxy();
    public String login(){
        userService.login("xiaohei""123456");
        return "success";
    }
    public String removeUser(){
        userService.removeUser(1);
        return "success";
    }
}
复制代码

2 Spring动态代理

静态代理的问题:

  1. 随着额外功能的增多,代理类数量随之增多,不利于管理
  2. 代理类冗余,存在多个代理类提供相同的功能

解决方案:动态代理

Spring动态代理:无需程序员手动编写代理类,只需要提供额外功能代码,然后由Spring框架自动的为原始类生成有增强功能的代理类。

好处:提高开发效率。

开发步骤:

额外引入以下2个依赖:spring-aop aspectjweaver

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.26.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
复制代码
  1. 创建原始类型对象

    <bean id="标识" class="原始(目标)类全类名"></bean>
    复制代码
  2. 定义额外功能。实现Spring的特定接口

    public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
        @Override
        /*
            method: 原始类型中方法
            args: 参数
            target: 原始对象
         */

        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("before...");
        }
    }
    复制代码
  3. 配置额外功能类

     <!-- 配置功能增强类 -->
     <bean id="myBeforeAdvice" class="com.bcl.advice.MyMethodBeforeAdvice"/>
    复制代码
  4. 定义切入点:决定额外功能添加到哪个位置

  5. 组装

    <aop:config>
        <!-- 定义切入点-->
        <aop:pointcut id="myPointCut" expression="execution(* com.bcl.service.*.*(..))"/>

        <!-- 组装 -->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="myPointCut"/>
    </aop:config>
    复制代码

名词(术语)解释:

  • 原始类(目标类):提供核心功能的类
  • 原始方法(目标方法):原始类中没有加入额外功能的方法
  • 额外功能(增强):用于增强原始方法的代码

3 Spring动态代理的实现流程

image-20200602114248141
image-20200602114248141

4 增强(Advice)

4.1 前置增强

增强会在目标方法前执行,实现接口 MethodBeforeAdvice

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    /*
        method: 原始方法
        args: 调用方法时的实参列表
        target: 原始对象
     */

    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("method = [" + method + "], args = [" + Arrays.toString(args) + "], target = [" + target + "]");
        System.out.println("target.getClass() = " + target.getClass());
        System.out.println("前置增强");
    }
}
复制代码

4.2 后置增强

增强代码会在目标方法正常return后执行,实现接口 AfterReturningAdvice

public class MyAfterReturningAdvice implements AfterReturningAdvice {
    /*
        returnValue: 原始方法正常结束后的返回值
        method: 原始方法
        args: 实参列表
        target: 原始对象
     */

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置增强");
    }
}
复制代码

4.3 异常增强

增强代码在目标方法发生异常时执行,实现接口 ThrowsAdvice

public class MyThrowsAdvice implements ThrowsAdvice {
    /*
        method: 原始方法
        args: 实参列表
        target: 原始对象
        throwable: 原始方法运行时的异常
     */

    public void afterThrowing(Method method, Object[] args,Object target, Throwable throwable){
        System.out.println("异常增强");
    }
}
复制代码

4.4 环绕增强

增强代码在目标方法前后以及异常时都可以执行,实现接口 MethodInterceptor

image-20200602144422389
image-20200602144422389
public class MyRoundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation pj) throws Throwable {
        System.out.println("前置增强");

        Object result = null;
        try {
            //放行:调用原始方法
            result = pj.proceed();

            System.out.println("后置增强");
        }catch(Exception e){
            System.out.println("异常增强");
            throw e;
        }finally{
            System.out.println("最终增强");
        }
        return result;
    }
}
复制代码

5 切入点详解

切入点:需要做功能增强处理的位置,可以通过切入点表达式描述。

5.1 execution表达式[重点]

image-20200602150619051
image-20200602150619051

实战中,使用 execution(* com.bcl.service.*.*(..)) 添加额外功能。

5.2 args表达式

用来匹配特定参数的方法。

args(参数列表)

5.3 within表达式

用来匹配特定的类,根据类名匹配。

within(类名表达式)

5.4 @annotation表达式

@annotation(注解类型),通过特定的注解来匹配类。

  1. 自定义一个注解

    public @interface MyAnnotation {
    }
    复制代码
  2. 使用自定义注解描述方法(实现类中的方法)

    public class UserServiceImpl implements UserService {
        @Override
        @MyAnnotation
        public boolean login(String username, String password) {
            System.out.println("username = [" + username + "], password = [" + password + "]");
            return false;
        }

        @Override
        public void removeUser(Integer id) {
            System.out.println("id = [" + id + "]");
            //int i = 10/0;
        }
    }
    复制代码
  3. 切入点配置为 @annotation(自定义注解类型)

    <aop:pointcut id="myPointCut" expression="@annotation(com.bcl.annotation.MyAnnotation)"/>
    复制代码

5.5 表达式的运算符

表达式之间可用过 and or not运算。

6 Spring AOP

AOP(Aspect Oriented Programming)面向切面编程。

OOP(Object Oriented Programming)面向对象编程。

6.1 OOP(面向对象编程)

面向对象编程以对象为单位进行编程,抽取共性方法,通过继承重用这些方法。

image-20200602153645027
image-20200602153645027

6.2 Spring AOP

AOP为了解决程序中零散的共性代码的复用问题,是OOP有力补充。

  1. 增强:共性代码,额外功能。比如:事务、性能分析
  2. 切入点:添加额外功能的方法的位置
  3. 织入(编织):将增强添加到切入点的过程
  4. 切面:在切点织入增强代码后形成的一个几何平面的概念
image-20200602154757972
image-20200602154757972

面向切面编程的要素:增强、切点和织入

面向切面编程的作用:灵活的以非侵入的方式(非耦合式)为现有的方法增强功能。

Spring切面编程的步骤:

  1. 配置原始类型对象
  2. 定义额外功能(增强)
  3. 配置增强类
  4. 定义切入点
  5. 编织

7 数据库中事务的隔离级别

事务的隔离级别:事务并发执行时,微观上多个执行时间相近的事务相互影响的问题。

标准的隔离级别4种:

隔离级别 特点 问题
READ_UNCOMMITTED 可以读取到未提交的事务 脏读
READ_COMMITTED 只能读到已经提交的事务 不可重复读
REPEATABLE_READ 同1个事务中读取到数据始终一致 幻影读
SERIALIZABLE 序列化读,不允许并发操作 性能差

Oracle数据库,只支持2种:READ——COMMITTEDSERIALIZABLE ,MySQL支持4种。

MySQL隔离级别的演示:

  1. 确认MySQL的存储引擎

    show engines;
    复制代码
  2. 查看隔离级别

     select @@session.tx_isolation;
    复制代码
  3. 设置隔离级别

    set @@session.tx_isolation=0 | 1 | 2 | 3
    复制代码
  4. 开启事务

    begin;
    复制代码
  5. 结束事务

    commit;
    rollback;
    复制代码

「❤️ 帅气的你又来看了我」

如果你觉得这篇内容对你挺有有帮助的话:

  1. 点赞支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)

  2. 欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。

  3. 觉得不错的话,也可以关注 编程鹿 的个人公众号看更多文章和讲解视频(感谢大家的鼓励与支持🌹🌹🌹)