Spring学习笔记-04 AOP

1.代理模式

代理模式的适用场景在于,有时候我们想对一些实体类的方法进行增强,但是又不方便修改实体类内部对应的方法定义,此时我们可以通过模式,将增强的部分写在代理中。

1)静态代理

假设我们想要卖手机,于是我们可以如下编写实体类:

Person.java

public class Person {
    public void sellPhone(){
        System.out.println("卖手机");
    }
}

测试:

public class TestStaticProxy {
    public static void main(String[] args) {
        Person person = new Person();
        person.sellPhone();
    }
}

现在我们发现在卖手机之前我们需要寻找下家,但是我们已经定义好了卖手机的方法,这时候我们可以通过静态代理的方式进行增强:

1)使用接口定义规范

public interface ISellPhone {
    public void sellPhone();
}

2)定义方法

public class Person implements ISellPhone {
    public void sellPhone(){
        System.out.println("卖手机");
    }
}

3)通过代理对方法进行增强

public class SellPhoneProxy implements ISellPhone {
    private Person person = new Person();
    public void sellPhone() {
        System.out.println("寻找买家");
        person.sellPhone();
    }
}

4)我们直接使用代理就好了

public class TestStaticProxy {
    public static void main(String[] args) {
        ISellPhone iSellPhone = new SellPhoneProxy();
        iSellPhone.sellPhone();
    }
}

2)JDK动态代理

静态代理的缺陷在于,几乎对于每一个业务,都得通过增加一个代理的方式来完成,即便他们的增强方式几乎一致;我们还可以使用动态代理的方式,直接增强方法而不需要编写很多的代理类。

我们增加一个手机售后的业务:

扫描二维码关注公众号,回复: 12346428 查看本文章
public interface ISellPhone {
    public void sellPhone();
    public void serviceAfterSelling();
}

Person.java

public class Person implements ISellPhone {
    public void sellPhone(){
        System.out.println("卖手机");
    }

    public void serviceAfterSelling() {
        System.out.println("售后服务");
    }
}

使用JDK的动态代理接口:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class SellPhoneProxy implements InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用前");
        Object result = method.invoke(new Person(),args);
        System.out.println("调用后");
        return result;
    }
}

看到包名我们也应该知道,动态代理是通过反射机制实现的。Object属性用于存储被代理的对象。

测试:

import java.lang.reflect.Proxy;

public class TestDynamicProxy {
    public static void main(String[] args) {
        Person person = new Person();
        ISellPhone iSellPhone = (ISellPhone) Proxy.newProxyInstance(ISellPhone.class.getClassLoader(),new Class[]{ISellPhone.class},new SellPhoneProxy());
        iSellPhone.serviceAfterSelling();
    }
}

[注意]:不论是静态代理,还是动态代理,代理类和被代理类需要有共同的接口。

3)CGLib动态代理

当然,还可以使用继承的方法,让代理类继承被代理类,通过覆写方法的方式增强对应的方法进行代理(cglib)。

A. 导入依赖

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

B. 使用cglib动态代理

import java.lang.reflect.Method;

public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class);
        enhancer.setCallback(new InvocationHandler() {
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                System.out.println("执行前");
                Object invoke = method.invoke(new Person(),objects);
                System.out.println("执行后");
                return invoke;
            }
        });
        Person personAgent = (Person) enhancer.create();
        personAgent.sellPhone();
        System.out.println("============");
        personAgent.serviceAfterSelling();
    }
}

3.执行结果

2.AOP 面向切面编程

AOP(Aspect Oriented Programming)面向切面编程,在程序开发的过程中主要用来解决一些系统层面上的问题,比如日志、事务、权限等

Spring的AOP实际上也是对方法的代理增强,经过上面的分析,我们已经知道,基于接口的代理,使用的是JDK;没有接口的,则通过cglib使用继承的方式进行代理。

我们先来看一下OOP(Object Oriented Programming)面向对象编程,把一切东西都看成对象,Java就是一门OOP语言,类的扩展用继承的方式来实现,用Java构建的系统由于继承,呈现出一种纵向的关系。

然而继承也有缺点,继承虽然可以完成对父类方法的增强,但是每一个类的增强都需要一次继承,这会导致整个系统的类过多,不便于维护,尤其是类似于日志这种对于所有的方法,增强方式几乎一致的时候,如果通过继承来实现,工作量是在过于庞大。

在Sring中,定义了一种”横切“技术,通过一个切面横向插进方法内部进行增强,而不需要继承,整个系统呈现出一种横向关系,其底层还是通过代理实现的,具有切面的效果如下:

A.与AOP相关的术语名词:

1. 通知、增强处理(Advice)

写好的用于实现功能增强的代码或逻辑,就是上述所说的安全、事务、日志等。

2. 连接点(JoinPoint)

Spring允许插入通知的地方,包括每个方法的前、后、环绕(前后都有)、抛出异常时,Spring只支持对方法的连接点。

3. 切入点(PointCut)

就是我们实际上定义要使用的那些连接点,也就是真正将通知切入的那些连接点。

4.  切面(Aspect)

切面就是通知和切入点的结合。通知负责说明要做什么、在什么时候做,切入点说明了在什么地方做,共同构成了切面。

5. 引入(Introduction)

引入允许我们把切面引入目标类。

6. 目标(Target)

引入中所提到的目标类,也就是要被通知的对象,具有真正的业务逻辑,它无需关注被切入了什么,只需要关注自己的业务逻辑。

7. 织入(Weaving)

把切面应用到目标对象(把通知切入待到切点上),依此来构建新的代理对象的过程。

B.感受AOP的使用(注解):

1. 加入头文件

<?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"
       xsi:schemaLocation="
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    
</beans>

2. 导入织入的相关依赖

        <!--引入织入相关的依赖-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

3.增加两个待增强的方法

AdminService.java

package com.zt.Service;

import org.springframework.stereotype.Service;

@Service
public class AdminService {

    public Integer getAdmin(){
        System.out.println("-----getAdmin-----");
        return 100;
    }

}

UserService.java

package com.zt.Service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void getUser(){
        System.out.println("-----getUser-----");
    }

}

4. 编写切面类

package com.zt.Aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LogAdvice {

    @Before("execution(* com.zt.Service.*.*(..))")
    public void before(){
        System.out.println("-----方法执行前 before -----");
    }

    @After("execution(* com.zt.Service.*.*(..))")
    public void after(){
        System.out.println("-----方法执行后 after -----");
    }

    @AfterReturning("execution(* com.zt.Service.*.*(..))")
    public void afterReturning(){
        System.out.println("-----方法执行返回后 afterReturning -----");
    }

    @Around("execution(* com.zt.Service.*.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("-----环绕前-----");
        System.out.println("方法名:" + joinPoint.getSignature());
        Object proceed = joinPoint.proceed();
        System.out.println("-----环绕后-----");
        System.out.println(proceed);
    }

    @AfterThrowing("execution(* com.zt.Service.*.*(..))")
    public void afterThrow(){
        System.out.println("-----有异常-----");
    }

}

[注]:execution表达式,用于指明对哪些方法进行增强。

当然,我们也可以具体指定对哪个方法进行增强:execution(void com.zt.Service.UserService.getUser(..))

5. 由于我们使用切面类的方式,因此需要开启扫包,让Spring容器管理这个用于配置的Bean

<context:component-scan base-package="com.zt"/>

6. 开启AOP自动代理的注解支持

<aop:aspectj-autoproxy/>

7. 使用配置类、并进行测试

package com.zt.Config;

import com.zt.Service.AdminService;
import com.zt.Service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class JavaConfig {

    @Autowired
    private UserService userService;

    @Autowired
    private AdminService adminService;

    @Test
    public void TestAop(){
        userService.getUser();
        System.out.println("-------------------------------------------");
        adminService.getAdmin();
    }

}

[注]:Spring只能对容器内的Bean的方法进行增强,在容器外实例化的对象并不会被增强,

C.AOP使用(配置文件方式)

1. 定义切面类

package com.zt.Aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

@Component
public class XMLAdvice {

    public void before(){
        System.out.println("-----方法执行前 before -----");
    }

    public void after(){
        System.out.println("-----方法执行后 after -----");
    }

    public void afterReturning(){
        System.out.println("-----方法执行返回后 afterReturning -----");
    }

    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("-----环绕前-----");
        System.out.println("方法名:" + joinPoint.getSignature());
        Object proceed = joinPoint.proceed();
        System.out.println("-----环绕后-----");
        System.out.println("方法执行结果的返回值:" + proceed);
    }

    public void afterThrow(){
        System.out.println("-----有异常-----");
    }
    
}

2. 在配置文件中,使用切入点,引用切面的方法进行织入

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
        <aop:aspect ref="XMLAdvice">
            <aop:before pointcut-ref="pointcut" method="before"/>
            <aop:after pointcut-ref="pointcut" method="after"/>
        </aop:aspect>
    </aop:config>

<aop:pointcut>:用来定义切入点。

<aop:aspect>:用于引入切面类。

<aop:before>:定义执行方式为执行前,引入切点既可以用ref引用(pointcut-ref)已经存在的切入点,也可以直接用execution表达式定义切入点(pointcut)。method用来指定通知来自切面中的哪个方法,其他的执行方式类似。


        <aop:aspect ref="XMLAdvice">
            <aop:before pointcut="execution(* com.zt.Service.*.*(..))" method="before"/>
            <aop:after pointcut="execution(* com.zt.Service.*.*(..))" method="after"/>
        </aop:aspect>

D. AOP使用(直接使用通知,通过实现某些特定的接口)

LogBefore.java

package com.zt.Aop;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
public class LogBefore implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----before------" + method.getName() + "-----");
    }
}

LogAfter.java

package com.zt.Aop;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
public class LogAfter implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----after-----" + method.getName() + "------");
    }
}

在配置文件中引用:

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
        <aop:advisor advice-ref="logBefore" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="logAfter" pointcut-ref="pointcut"/>
    </aop:config>

[注]:使用@Component注解可以让Spring自动实例化并管理这个Bean,且默认命名为首字母小写,其它保持不动(驼峰命名法)。当然,为了可读性我们也可以指定注入后的bean id:

LogBefore.java

package com.zt.Aop;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component("Before")
public class LogBefore implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----before------" + method.getName() + "-----");
    }
}

LogAfter.java

package com.zt.Aop;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component("After")
public class LogAfter implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----after-----" + method.getName() + "------");
    }
}

在配置文件中引用:

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
        <aop:advisor advice-ref="Before" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="After" pointcut-ref="pointcut"/>
    </aop:config>

猜你喜欢

转载自blog.csdn.net/qq_39304630/article/details/112392223
今日推荐