Spring的AOP

AOP相关简介 

AOP:预编译,在运行期执行的。

动态代理是在不修改源码的情况下,对代码进行相应的增强。可以完成程序功能上松耦合。

AOP和动态代理功能差不多,且不要自己写动态代理的代码了。

AOP松耦合,抽取功能,减少重复代码,提高开发效率,且便于维护。

Spring采用动态代理技术。AspectJ采用编译器植入和类装载期织入。

AOP底层:spring集成了jdk代理技术和gclib代理技术。

jdk代理:目标对象基于目标接口生成代理对象放入运行时的内存中。

cglib代理:为目标对象动态生成子对象,但这个子对象比父对象功能更强大,不是继承是动态生成基于目标对象的对象,放入运行时内存中。

在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式(jdk\cglib)。

 AOP相关术语

Target(目标对象:代理的目标对象

Proxy(代理):一个类被AOP织入增强后,禅城一个结果代理类

JoinPoint(连接点):被拦截到的点(方法)。可以被增强的方法叫做连接点。

Pointcut(切入点):即我们要对哪些JoinPoint进行拦截。

注:切入点是真正被增强的方法,被选中增强的连接点才是切入点。连接点和切入点的关系就像公民和人大代表一样。

Advice(通知/增强):即拦截到JoinPoint后要做的事情。即用于增强功能额外功能的代码。

Aspect(切面):切入点和通知的结合。切面=目标方法+增强方法。

Weaving(织入):把增强用于目标对象来创建新的代理对象的过程。织入是个动词,可以理解为切点和增强(通知)结合的过程就是织入。

AOP技术实现内容

 Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,则使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

举例

比如日志控制

传统的抽取日志控制代码:在业务方法中还需要引用如save(user)、update(user)之类调用抽取出来的日志控制代码,还是出现耦合。

使用AOP:

将业务方法作为切入点,将日志控制代码作为增强,通过对业务方法使用配置文件或注解,使得在运行业务方法(切点)前,通过spring动态代理技术对切点进行增强的织入,基于目标对象生成代理对象。运行时代理对象的根据增强类型先后,调用增强代码和目标对象的切点方法即可。这样,目标方法和日志控制完全解耦,仅在运行时结合在一起。

快速入门

基于jdk的动态代理

创建一个mavenWeb项目(cglib需要web环境)。
 com.kdy.proxy.jdk包中创建TargetInterface目标类接口
public interface TargetInterface {
    public void save();
}
com.kdy.proxy.jdk包中创建Target(目标类接口实现类)实现TargetInterface接口
public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("save running...");
    }
}
 com.kdy.proxy.jdk包中创建Advice增强类
public class Advice {
    //前置增强
    public void before(){
        System.out.println("before...");
    }
    //后置增强
    public void afterReturning(){
        System.out.println("afterReturning...");
    }
}
 com.kdy.proxy.jdk包中创建测试类ProxyTest
public class ProxyTest {
    public static void main(String[] args) {
        //目标对象
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();
        //基于目标对象动态生成代理对象:返回值 就是动态生成的代理对象
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//目标对象类加载器
                target.getClass().getInterfaces(),//目标对象相同的接口字节码对象数组
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before();//前置增强
                        Object invoke = method.invoke(target, args);//执行目标方法
                        advice.afterReturning();//后置增强
                        return invoke;
                    }
                }
        );
        proxy.save();
    }
}
运行main方法即可。

基于cglib的动态代理

上面项目pom我呢见引入spring依赖(spring依赖中包含cglib包)
        <dependency><!--spring依赖包含cglib包-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
com.kdy.proxy.jdk包中创建Target(目标类)
public class Target {
    public void save(){
        System.out.println("save running......");
    }
}
 com.kdy.proxy.jdk包中创建Advice增强类
public class Advice {
    //前置增强
    public void before(){
        System.out.println("before...");
    }
    //后置增强
    public void afterReturning(){
        System.out.println("afterReturning...");
    }
}
 com.kdy.proxy.jdk包中创建测试类ProxyTest
public class ProxyTest {
    public static void main(String[] args) {
        //目标对象
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();

        //返回值 就是动态生成的代理对象、基于cglib
        //1、创建增强器
        Enhancer enhancer = new Enhancer();
        //2、设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3、设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                advice.before();//执行前置
                Object invoke = method.invoke(target, args);//执行目标
                advice.afterReturning();//执行后置
                return invoke;
            }
        });
        //创建代理对象
        Target proxy = (Target)enhancer.create();

        proxy.save();
    }
}
运行main方法即可。

面向切面编程-AOP开发 

基于XML的AOP开发

快速入门

创建mavenWeb项目
引入spring、aspectJ、junit、spring-test依赖
 <dependencies>
        <dependency><!--spring依赖包含cglib包-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>
在com.kdy.aop包中创建TargetInterface接口和Target目标类
public class Target implements TargetInterface{
    public void  save(){
        System.out.println("save running...");
//        int i =  1/0;//异常抛出
    }
}
在com.kdy.aop包中创建MyAspect切面类
public class MyAspect {
    //前置增强
    public void before() {
        System.out.println("before...");
    }
    //后置增强
    public void afterReturning() {
        System.out.println("afterReturning...");
    }
    //环绕增强
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("before around...");//环绕前增强
        Object proceed = pjp.proceed();//切点方法
        System.out.println("after around...");//环绕后
        // 增强
        return proceed;
    }
    //异常抛出增强
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }
    //最终增强
    public void after() {
        System.out.println("after...");
    }
}
resource下applicationContext配置文件创建目标类的bena和切面类的bean,并配置织入
<?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/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
">
    <!--目标对象-->
    <bean id="target" class="com.kdy.aop.Target"></bean>
    <!--切面对象-->
    <bean id="myAspect" class="com.kdy.aop.MyAspect"></bean>
    <!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面:切点+通知--><!--method="before"表示增强/通知,aop:before表示需要前置增强操作
            pointcut="execution(public void com.kdy.aop.Target.save())为切点,execution为切点表达式-->
       <!--     <aop:before method="before" pointcut="execution(public void com.kdy.aop.Target.save())"/>-->
            <aop:before method="before" pointcut="execution(* com.kdy.aop.*.*(..))"/>
            <aop:after-returning method="afterReturning" pointcut="execution(* com.kdy.aop.*.*(..))"/>
            <aop:around method="around" pointcut="execution(* com.kdy.aop.*.*(..))"/>
            <aop:after-throwing method="afterThrowing" pointcut="execution(* com.kdy.aop.*.*(..))"/>
            <aop:after method="after" pointcut="execution(* com.kdy.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

切点表达式:

execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 

修饰符可省略,返回值类型、包名、类名、方法名可用*代表任意

包名和类名之间一个点代表当前包下的类,两个点..代表当前包及其子包下的类

参数列表可使用两个点..表示任意个数,任意类型的参数列表

xml中的切点表达式除了写全,还可简写举例如下:

execution(void com.kdy.aop.Target.*(..))      execution(* com.kdy.aop.*.*(..))

execution(* com.kdy.aop..*.*(..))  execution(* com.kdy.aop.*.*(..))  execution(* *..*.*(..))

一般常用   execution(* com.kdy.aop.*.*(..))

通知类型:

前置通知:<aop:before>  用于配置前置通知。指定增强的方法在切入点方法之前执行。
后置通知:<aop:after-returning>配置后置通知。指定增强的方法在切入点方法之后执行。
环绕通知:< aop:around>用于环绕通知。指定增强的方法在切入点方法之前和之后都执行。
异常抛出通知:<aop:throwing>配置异常抛出通知。指定增强的方法在出现异常时执行。
最终通知:<aop:after>用于配置最终通知。无论增强方式执行是否有异常都会执行。

在test目录中写测试类,注入目标类,直接调用目标类的方法,运行测试用例即可
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    /*
    * 注:这里要注入接口TargetInterface,因为xml中声明的是Target实现类的bean,但经过织入生成代理对象的bean是最终要注入进来的
    * 因为Target有接口所以spring基于jdk来动态代理生成代理对象,这个代理对象也是TargetInterface接口类型的,
    * 所以spring生命周期中最终通过TargetInterface注入时找到的是动态代理对象的bean,将其注入进来
    * */
    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}

注:@Autowired注入的类型为接口,就是考虑到spring中aop,虽然类型为实现类也行,但aop的时候必须注入类型为接口,所以通常平时都注入接口类型。若某个注入接口有多实现类,可用配合@Qualify或直接用@Resource。

执行顺序增强类型都是相对切点方法的先后属顺序。

applicationContext.xml织入中抽取切点表达式的写法为:
<?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/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
">
    <!--目标对象-->
    <bean id="target" class="com.kdy.aop.Target"></bean>
    <!--切面对象-->
    <bean id="myAspect" class="com.kdy.aop.MyAspect"></bean>
    <!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--抽取切点表达式-->
            <aop:pointcut id="myPoint" expression="execution(* com.kdy.aop.*.*(..))"/>
            <aop:before method="before" pointcut-ref="myPoint"/>
            <aop:after-returning method="afterReturning" pointcut-ref="myPoint"/>
            <aop:around method="around" pointcut-ref="myPoint"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPoint"/>
            <aop:after method="after" pointcut-ref="myPoint"/>
        </aop:aspect>
    </aop:config>
</beans>

基于注解的AOP开发

快速入门

mavenWeb项目,pom文件引入spring、aspectJ、junit、spring-test依赖
 <dependencies>
        <dependency><!--spring依赖包含cglib包-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>
在com.kdy.anoo包中创建Target目标类实现目标类接口TargetInterface,并使用spring注解声明bean。
@Component("target")
public class Target implements TargetInterface {
    public void  save(){
        System.out.println("save running...");
//        int i =  1/0;//异常抛出
    }
}
在com.kdy.anoo中创建MyAspect,并使用spring注解声明bean,并使用@Aspect声明为切面类
@Component("myAspect")
@Aspect//切面类
public class MyAspect {
    //抽取切点表达式
    @Pointcut("execution(* com.kdy.anno.*.*(..))")
    public void myPointcut(){}
    //前置增强
    @Before("myPointcut()")//使用上面抽取的切点表达式
    public void before() {
        System.out.println("before...");
    }
    //后置增强
    @AfterReturning("MyAspect.myPointcut()")//使用上面抽取的切点表达式
    public void afterReturning() {
        System.out.println("afterReturning...");
    }
    //环绕增强
    @Around("execution(* com.kdy.anno.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("before around...");//环绕前增强
        Object proceed = pjp.proceed();//切点方法
        System.out.println("after around...");//环绕后
        // 增强
        return proceed;
    }
    //异常抛出增强
    @AfterThrowing("execution(* com.kdy.anno.*.*(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }
    //最终增强
    @After("execution(* com.kdy.anno.*.*(..))")
    public void after() {
        System.out.println("after...");
    }
}
resource下创建applicationContext.xml
<?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 http://www.springframework.org/schema/context/spring-context.xsd
">
    <!--配置spring组件扫描-->
    <context:component-scan base-package="com.kdy.anno"/>
    <!--配置aop自动代理,@Aspect注解才会被扫描到-->
    <aop:aspectj-autoproxy/>
</beans>
 test目录下的测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    /*
    * 注:这里要注入接口TargetInterface,因为xml中声明的是Target实现类的bean,但经过织入生成代理对象的bean是最终要注入进来的
    * 因为Target有接口所以spring基于jdk来动态代理生成代理对象,这个代理对象也是TargetInterface接口类型的,
    * 所以spring生命周期中最终通过TargetInterface注入时找到的是动态代理对象的bean,将其注入进来
    * */
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.save();
    }
}

其实也可以创建如applicationContext-anno.xml,并复制以上applicationContext.xml中的内容,并在test的测试类中上方的改成@ContextConfiguration("classpath:applicationContext-anno.xml")。

运行测试用例即可。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值