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

2020-07-30 14:31:10 浏览数 (1)

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

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

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

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

1.2 代理模式

image-20200602102246281

矛盾:

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

矛盾的解决方案:中介

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

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

1.3 静态代理

image-20200602103550535

实战:

接口:

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

原始实现类:

代码语言:javascript复制
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   "]");
    }
}

代理实现类:

代码语言:javascript复制
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("结束事务");
    }
}

使用者:

代码语言:javascript复制
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

代码语言:javascript复制
<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

4 增强(Advice)

4.1 前置增强

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

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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

实战中,使用 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

6.2 Spring AOP

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

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

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. 觉得不错的话,也可以关注 编程鹿 的个人公众号看更多文章和讲解视频(感谢大家的鼓励与支持

    0 人点赞