代理模式及SpringAop的应用

设计模式——代理模式

学习目标:

掌握代理模式,理解Spring Aop并能运用

学习内容:

1、 静态代理
2、JDK动态代理
3、CGLib动态代理
4、Spring Aop 基于xml
5、Spring Aop 基于注解



一、代理模式的定义

定义:Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)
Subject抽象主题角色:
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
RealSubject具体主题角色:
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
Proxy 代理主题角色:
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

UML 类图

在这里插入图片描述

二、静态代理的实现

1.创建SomeService接口

代码如下:

public interface SomeService {
    public void doSome();
    public void doOther();
}

2.创建Someservice的实现类

代码如下:

public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("执行业务方法doSome");
    }

    @Override
    public void doOther() {
        System.out.println("执行业务方法doOther");
    }
}

3.创建事务类

代码如下:

//事务类
public class Transaction {
    //开启事务
    public static void doLog(){
    System.out.println("非业务方法,开启事务,方法执行的时间" +new Date());
}
    //提交事务
    public static void doTrans(){
        System.out.println("非业务方法,方法执行完毕,提交事务");
    }
}

4.创建代理类

代码如下:

public class SomeServiceProxy implements SomeService {
    //目标对象,真实类
    SomeServiceImpl service = new SomeServiceImpl();
    @Override
    public void doSome() {
        //调用事务方法
        Transaction.doLog();
        service.doSome();
        Transaction.doTrans();
    }

    @Override
    public void doOther() {
        Transaction.doLog();
        service.doOther();
        Transaction.doTrans();
    }
}

5.测试

代码如下:

public class StaticProxyTest {
    @Test
    public void test(){
        //产生静态代理对象
        SomeServiceProxy s1 = new SomeServiceProxy();
        s1.doSome();
        s1.doOther();
    }
}

6.运行截图

在这里插入图片描述


三JDK动态代理

与静态类似,我们写的代理类由JDK帮我们写好了我们只需要调就可以了;需要我们注意的是JDK代理类必须要实现一个接口才能代理。

JDK代理类的实现

代码如下:

public class MyHandler implements InvocationHandler {
    //目标对象
    private Object target;
    //SomeSeImpl类,初始化目标对象
    public MyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //开启事务
        SomeTools.doLog();
        //反射对象
        Object result = method.invoke(target,args);
        //提交事务
        SomeTools.doTrans();
        return result;
    }
}

测试

代码如下:

public class testProxy {
    @Test
    public void test1(){
        //目标对象
        SomeService target = new SomeServiceImpl();
        //多态性创建拦截器handler对象
        InvocationHandler  handler = new MyHandler(target);
        //三个参数的含义:目标的类加载器、目标实现的所有接口(反射对象)、拦截器handler对象、
        SomeService proxy =(SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
        //proxy不是真实对象,是代理后的对象
        System.out.println("proxy =========" +proxy.getClass().getName());
        proxy.doSome();
        proxy.doOther();
    }
}

运行截图

在这里插入图片描述

四、CGLib动态代理

GCLib采用底层的字节码技术给目标类,自动生成一个子类!子类重写父类的方法,使用子类对象调用方法的时候,本质调用子类的方法!

导入依赖

<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

CGLib代理类的实现

//拦截器
public class MyCglibHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] agrs, MethodProxy methodProxy) throws Throwable {
        //开启事务
        SomeTools.doLog();
        Object result = methodProxy.invokeSuper(o,agrs);
        //提交事务
        SomeTools.doTrans();
        return result;
    }
}

#测试

代码如下:

public class TestCglib {
    @Test
    public void test(){
        MyCglibHandler handler = new MyCglibHandler();
        Enhancer enhancer = new Enhancer();
        //根据父类生成子类
        enhancer.setSuperclass(SomeServiceImpl.class);
        //调用代理对象的方法
        enhancer.setCallback(handler);
        //生成子类代理类,o是SomeServiceImpl的子类对象,
        SomeServiceImpl o =(SomeServiceImpl) enhancer.create();
        o.doSome();
        o.doOther();
    }
}

运行截图

在这里插入图片描述

五、代理模式运用Spring AOP

AOP介绍

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志打印、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

1、AOPxml配置:

导入依赖:

<properties>
        <spring.vension>5.3.6</spring.vension>
        <junit.version>4.13.2</junit.version>
        <log4j.version>1.2.14</log4j.version>
        <lombok.version>RELEASE</lombok.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.vension}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.vension}</version>
        </dependency>
         <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

先创建USerService接口

public interface UserService {
    int addUser(User u);
    int delete(int id);
}

USerService实现类

public class UserServiceImpl implements UserService {
   @Override
    public int addUser(User u) {
        System.out.println("模拟DAO层的新增方法" + u);
        return 1;
    }
    @Override
    public int delete(int id) {
        System.out.println("模拟DAO层的删除方法" + id);
        return 1;
    }
}

通知(增强)类

//通知(增强)类
public class MyAdvice {
    public void before(){
        System.out.println("前置通知(前置增强)...");
    }
    public void after(){
        System.out.println("后置通知(后置增强)...");
    }
}

applicationContext.xml

<!--被代理的目标对象 -->
    <bean  class="com.spring.service.impl.UserServiceImpl"/>
    <!--配置通知对象 -->
    <bean id="myAdvice" class="com.spring.advice.MyAdvice"/>
    <!-- 配置将增强织入目标对象 -->
    <aop:config>
        <!--切入点:哪个类的哪个方法需要被代理-->
        <aop:pointcut id="pc" expression="execution(* com.spring.service.impl.UserServiceImpl.addUser(..))"/>
        <!--配置切面-->
        <aop:aspect ref="myAdvice">
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="pc"></aop:before>
            <!--后置通知-->
            <aop:after-returning method="after" pointcut-ref="pc"></aop:after-returning>
        </aop:aspect>
    </aop:config>
}

环绕通知、异常通知、最终通知的xml配置类似。
前置通知:目标方法运行之前调用
后置通知(如果出现异常不会调用):在目标方法运行之后调用
环绕通知:在目标方法之前和之后都调用
异常拦截通知:如果出现异常,就会调用
最终通知(无论是否出现 异常都会调用):在目标方法运行之后调用

测试

public class TestAopXml {
    @Test
    public void test(){
        //启动Spring容器,加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从Spring容器中获取对象
        UserService u = context.getBean(UserService.class);
        int count = u.addUser(new User(101, "法外狂徒张三"));
        System.out.println(count);
    }
}

运行截

在这里插入图片描述

2、注解配置

导入依赖

和XML配置导入的依赖差不多,加一个spring-test的依赖,方便测试用

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>

创建配置类AppConfig

@Configuration
@ComponentScan("com.spring")
@EnableAspectJAutoProxy
public class AppConfig {
}

扫描当前的com.spring包下的所有类;

使用注解实现日志文件

@Component
@Aspect
public class LogAspect {
    /*
      第1个 * 表示方法的任意返回值
      第2个 * 表示com.spring.dao包中的所有类
      第3个 * 表示以add开头的所有方法
      ..  括号中的两点,表示任意参数
     */
    @Pointcut("execution(* com.spring.dao.*.add*(..))||"+
            "execution(* com.spring.dao.*.delete*(..))||"+
            "execution(* com.spring.dao.*.query*(..))")
    public void pointCut(){}

    @Before("pointCut()")
    public void logStart(JoinPoint jp){
        System.out.println(jp.getTarget());
        Logger.info("加入日志");
    }
    @After("pointCut()")
    public void lodEnd(JoinPoint jp){
        Logger.info("方法调用结束加入日志");
    }
}

@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestAopAnnotation {
    @Autowired
    private UserController controller;
    @Test
    public void test(){
        User u = new User(1001,"法外狂徒张三");
        controller.setUser(u);
        controller.add();
        controller.delete();
        controller.query();
    }
}

运行截图

在这里插入图片描述
使用注解配置更加的简洁,但是理解起来比较困难一点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值