Spring学习总结之代理模式与AOP

一. 什么是代理?

代理其实就和生活中的中介是一样的,假如你要去租房,肯定一头雾水,这时候你想到了找租房的中介,此时你并不会与租房的人进行交涉,而是中介代理租房人和你去谈租房的事,比如签订合同,交租金等。Spring也采用了这样的代理模式,比如将对象交给Spring去进行管理,此时Spring会生成一个代理对象来引用原始的对象。

二. 静态代理

角色分析:

  • 抽象角色:一般使用接口或者抽象类
  • 代理角色:代理真实角色
  • 真实角色:被代理的角色

就拿租房来说,中介代理房东去租房,客户询问中介,定义一个客户类Client.java,代理租房接口Renter.java,代理类Proxy.java,房东类Host.java来模拟代理模式。

Host.java: 提供房源

/**
 * @author jektong
 * @Date 2020/6/20-11:19
 */
//房东
public class Host implements Renter {
    
    
    @Override
    public void rent() {
    
    
        System.out.println("房东出租房子给客户");
    }
}

Renter.java:租房业务接口类

/**
 * @author jektong
 * @Date 2020/6/20-11:17
 */
public interface Renter {
    
    
    //租房
     void rent();
}

Proxy.java:中介代理房东租房处理业务

/**
 * @author jektong
 * @Date 2020/6/20-11:17
 */
//中介代理房东
public class Proxy implements Renter {
    
    
    private Host host;
    public Proxy(Host host){
    
    
        this.host = host;
    }
    @Override
    public void rent(){
    
    
      System.out.println("通知房东出租房屋");
	  renter.rent();
	  System.out.println("客户来提房");
    }
}

Client.java: 客户找中介租房

/**
 * @author jektong
 * @Date 2020/6/20-11:24
 */
public class Client {
    
    
    public static void main(String[] args) {
    
    
    	//房东出租房子
        Host host = new Host();
        //中介代理出租即可
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}

在这里插入图片描述

使用代理模式的好处:

  • 纯粹的操作真实对象,公共业务不用去关注
  • 公共业务交给代理处理,实现业务的分工
  • 业务扩展时可以集中管理

当然此模式也有缺点

一个真实角色就会有一个代理角色,导致效率开发变低

下面引出动态代理:

三. 动态代理

  • 动态代理的角色和静态代理角色一样
  • 代理的类是自动生成,不是自己写的
  • 基于接口类的代理和基于抽象类的代理
  • 代理方式:JDK代理,类动态代理,JavaSist代理

JDK动态代理

使用动态代理只需要关注两个类
Proxy: 生成代理类
InvocationHander: 处理实现的接口

现在用动态代理的方式去实现以上的案例:创建生成动态代理的类ProxyInvocationHander.java去代替上面静态的Proxy.java.

/**
 * @author jektong
 * @Date 2020/6/25-20:19
 */
//动态代理,自动生成代理类
public class ProxyInvationHander implements InvocationHandler {
    
    

    private Renter renter;

    public void setRenter(Renter renter) {
    
    
        this.renter = renter;
    }

    //生成代理类,固定代码
    public Object getProxy(){
    
    
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(), renter.getClass().getInterfaces(),this::invoke);
    }
    //处理代理的实例
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        Object result = method.invoke(renter, args);
        return result ;
    }
}

Client.java这个类也需要进行修改

	/**
 * @author jektong
 * @Date 2020/6/20-11:24
 */
public class Client {
    
    
    public static void main(String[] args) {
    
    
        //真实角色
        Host host = new Host();
        //代理角色,并没有写自动生成
        ProxyInvationHander pih = new ProxyInvationHander();
        //设置代理角色
        pih.setRenter(host);
        //生成代理对象
        Renter proxy = (Renter)pih.getProxy();
        //代理执行方法
        proxy.rent();
    }
}

在这里插入图片描述
这里我们可以不用改变原始的代码结构,自己去添加一些业务逻辑在上面,比如日志代码,还可加上交租金的业务啊之类的,这就是AOP(面向切面编程)的体现。

使用动态代理时,只需要使用一个动态代理类就可以代理多个类,而且代理的是接口。

可以将上面的类改成一个通用的代理类,这样就可以实现代理多个类了,我们只需要修改ProxyInvocationHander.java这个类就可以,使它成为一个工具类,注意对比

/**
 * @author jektong
 * @Date 2020/6/25-20:19
 */
//动态代理,自动生成代理类
public class ProxyInvationHander implements InvocationHandler {
    
    
    //代理接口
    private Object target;

    public void setTarget(Object target) {
    
    
        this.target = target;
    }
    //生成代理类
    public Object getProxy(){
    
    
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this::invoke);
    }
    //处理代理的实例
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        Object result = method.invoke(target, args);
        return result;
    }
}

动态代理模式说完之后,我们就可以引入AOP的概念了

四. 什么是AOP

AOP(面向切面编程)采取的是横向的抽取机制,将分散在各个方法中的重复代码抽取出来。当程序运行的时候再将其提取出来运用到要执行的代码上,提高了代码的重用性。其中类与切面的关系如下:
在这里插入图片描述
这张图就很好的反映了切面编程的实现方式,每个方法中都可以添加其它的业务代码,比如事务,日志,权限异常等,并且不会改变原有的代码结构,这就是AOP主要的实现思想。

AOP的相关术语以及在Spring中的作用

  • Aspect(切面):是切入点Pointcut和通知的Advice的集合。(比如加入的Log日志)
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。(与Pointcut一样表示在哪个地方执行)
  • Pointcut(切入点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(通知):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。(Log中的方法)
  • Target(目标类):织入 Advice 的目标对象.。(通知的对象比如接口或者方法)
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

五. AOP实现方式一(原生态方式实现)

首先导入AOP依赖(还有一些Spring的基本依赖)

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

需求: 定义用户接口以及它的实现类,通过AOP将日志的功能添加进去

用户接口以及他的实现类(UserService和UserServiceImpl):

/**
 * @author jektong
 * @Date 2020/6/26-20:11
 */
public interface UserService {
    
    
    void selUser();
    void addUser();
    void updUser();
    void delUser();
}

/**
 * @author jektong
 * @Date 2020/6/26-20:05
 */
public class UserServiceImpl implements UserService {
    
    
    @Override
    public void selUser() {
    
    
        System.out.println("查询用户信息");
    }
    @Override
    public void addUser() {
    
    
        System.out.println("添加用户信息");

    }
    @Override
    public void updUser() {
    
    
        System.out.println("修改用户信息");
    }
    @Override
    public void delUser() {
    
    
        System.out.println("删除用户信息");
    }
}

定义一个日志通知: AfterLog.java用于切入业务层的方法中

/**
 * @author jektong
 * @Date 2020/6/26-20:42
 */
//定义日志的后置通知
public class AfterLog implements AfterReturningAdvice {
    
    
    /**
     * @param returnValue 目标方法执行后的返回值
     * @param method 目标方法
     * @param objects 对象
     * @param o1
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
    
    
        System.out.println("执行了" + method.getName() + "方法返回的结果为:" + returnValue);
    }
}

applicationContext.xml的配置: 首先要引入aop的命名空间

<?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 https://www.springframework.org/schema/context/spring-context.xsd">
    <!--注册相关的bean-->
    <bean id="userService" class="service.Impl.UserServiceImpl"/>
    <bean id="afterLog" class="log.AfterLog"/>
    <!--配置aop-->
    <aop:config>
        <!--切入点 expression写位置-->
        <aop:pointcut id="pointcut" expression="execution(* service.Impl.UserServiceImpl.*(..))"/>
        <!--执行日志通知-->
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

关于execution中的表达式的写法解释:

execution括号里面写的是要切入的方法,可以是一个,也可以是多个方法

第一个 * 号:表示返回类型,* 号表示所有的类型,比如返回的是方法,后面跟的是切入到哪个包下的方法。比如这里切入的是UserServiceImpl下的所有方法,就用 * 表示,(…)这个表示方法带有那些参数。

测试

/**
 * @author jektong
 * @Date 2020/6/26-21:11
 */
//测试
public class SpringTestDemo {
    
    
    public static void main(String[] args) {
    
    
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService)ac.getBean("userService");
        userService.addUser();
    }
}

结果:添加的日志功能就实现了
在这里插入图片描述
以上实现AOP的方式是用API接口的方式实现的,我们也可以自定义类去实现,发现会更加的简单

六. AOP实现方式二(自定义类的方式实现)

根据以上的案例,增加一个Log.java类这个就是 我们要切入到业务方法的自定义的类

Log.java

/**
 * @author jektong
 * @Date 2020/6/27-13:43
 */
public class Log {
    
    
    public void before(){
    
    
        System.out.println("方法执行前");
    }
    public void after(){
    
    
        System.out.println("方法执行后");
    }
}

接下来修改一下xml的配置文件:

    <!--注册bean-->
    <bean id="userService" class="service.Impl.UserServiceImpl"/>
    <bean id="log" class="log.Log"></bean>
    <aop:config>
        <!--自定义切面-->
        <aop:aspect ref="log">
            <!--切入点-->
            <aop:pointcut id="pointcut" expression="execution(* service.Impl.UserServiceImpl.*(..))"/>
            <!--通知介入,此时我们可以自定义切入了,比如在切入点之前执行before()
            ,或者在 切入点之后执行after()-->
            <aop:before method="before" pointcut-ref="pointcut"></aop:before>
            <aop:after method="after" pointcut-ref="pointcut"></aop:after>
        </aop:aspect>
    </aop:config>

执行:

在这里插入图片描述

建议使用第二种方式,灵活度比较高

猜你喜欢

转载自blog.csdn.net/qq_41857955/article/details/106869524