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

时间:2022-07-22
本文章向大家介绍「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
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>
  1. 注解开发 Component(Controller、Service、Repository)替换bean标签 Autowired替换property标签

Spring AOP

1 代理设计模式

1.1 解决的问题

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

业务调用者的角度:需要,业务方法中需要使用这些额外功能 软件设计者的角度:不需要,定义后会造成代码的频繁修改

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

1.2 代理模式

矛盾:

房东不愿意提供额外功能(带看房,车接车送),因为麻烦 租客必须要使用这些额外功能

矛盾的解决方案:中介

中介代理了房东的出租房屋的方法,同时提供额外的功能。 房东无需亲自和租客打交道,节省时间。租客也可以享受完整的服务。

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

1.3 静态代理

实战:

接口:

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>
创建原始类型对象
<bean id="标识" class="原始(目标)类全类名"></bean>
定义额外功能。实现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...");
    }
}
配置额外功能类
 <!-- 配置功能增强类 -->
 <bean id="myBeforeAdvice" class="com.bcl.advice.MyMethodBeforeAdvice"/>
  1. 定义切入点:决定额外功能添加到哪个位置
组装
<aop:config>
 <!-- 定义切入点-->
 <aop:pointcut id="myPointCut" expression="execution(* com.bcl.service.*.*(..))"/>

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

名词(术语)解释:

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

3 Spring动态代理的实现流程

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

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表达式[重点]

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

5.2 args表达式

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

args(参数列表)

5.3 within表达式

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

within(类名表达式)

5.4 @annotation表达式

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

自定义一个注解
public @interface MyAnnotation {
}
使用自定义注解描述方法(实现类中的方法)
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;
    }
}
切入点配置为 @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(面向对象编程)

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

6.2 Spring AOP

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

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

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

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

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;