Spring框架AOP增强,动态代理

Posted 想一起听海

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架AOP增强,动态代理相关的知识,希望对你有一定的参考价值。

为什么需要AOP

AOP全名(Aspect-oriented programming)面向切面编程大法,它有很多兄弟,分别是经常见的面向对象编程,朴素的面向过程编程和神秘的函数式编程等。

AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理。本文就主要讲解AOP的基本术语,最后介绍一下AOP的两种代理方式:

(1)以AspectJ为代表的静态代理。

(2)以Spring AOP为代表的动态代理。

AOP的基本概念

AOP:面向切面编程,不修改源代码的基础上对代理对象的功能进行增强,要指定增强位置配置该位置做的事情

 1.切面(Aspest)

一个切面能够包含同一个类型的不同增强方法.切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中,(使用 @Aspect 注解的类就是切面)。

2.目标对象(Target)

目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。

3. 连接点(JoinPoint)

程序执行过程中明确的点,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。

4.增强处理

Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。

5.切入点(PointCut)

切入点是对连接点进行拦截的条件定义。

深入理解AOP增强

1.导入所需的jar包

 2.分层结构

 3.创建一个类,这个类完成核心功能。

(1)创建接口

package com.xue.service;
/*
* 演员接口
* */
public interface Actor 
    //唱
    void sing();
    //跳
    void dance();
    //rap
    void rap();
    //篮球
    void baskball();


(1)实现接口

package com.xue.service.Impl;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import com.xue.service.Actor;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

public class IKUN implements Actor 
    @Override
    public void sing() 
        System.out.println("只因你太美");
    

    @Override
    public void dance() 
        System.out.println("迎面走来的你");
    

    @Override
    public void rap() 
        System.out.println("让我如此蠢蠢欲动");
    

    @Override
    public void baskball() 
        System.out.println("这种感觉我从未有");
    



这个类可以被其他类实例化,并调用实现的方法。

4.创建Loger类,用来做功能的增强。

package com.xue.logger;

public class Logger 
    public void beforePrintLog()
        System.out.println("前置通知:beforePrintLog执行...");
    
    public void afterReturningPrintLog()
        System.out.println("后置通知:afterReturningPrintLog执行...");
    
    public void afterThrowingPrintLog()
        System.out.println("异常通知:afterThrowingPrintLog执行...");
    
    public void afterPrintLog()
        System.out.println("最终通知:afterPrintLog执行...");
    
    

这个类可以被其他类实例化,并调用其中的方法。通常这些方法会在AOP编程中被调用,用于给程序添加额外的功能或行为。

5.配置Spring的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"
       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="logger" class="com.xue.logger.Logger"/>
    <bean id="ikun" class="com.xue.service.Impl.IKUN"/>

    <aop:config>
        <aop:aspect id="xxs" ref="logger">
            <aop:before method="beforePrintLog" pointcut="execution(void com.xue.service.Impl.IKUN.sing())"/>
            <aop:after-returning method="afterReturningPrintLog" pointcut="execution(void com.xue.service.Impl.IKUN.dance())"/>
            <!-- 定义了一个名为afterThrowingPrintLog的方法,使用了切入点表达式execution(* *..CxkServiceImpl.*(..)) -->
            <aop:after-throwing method="afterThrowingPrintLog"  pointcut="execution(void com.xue.service.Impl.IKUN.rap())"/>
            <aop:after method="afterPrintLog"  pointcut="execution(* *..ikun.baskball(..))"/>
         </aop:aspect>
    </aop:config>

</beans>

6.测试方法

package com.xue.servlet;

import com.xue.service.Actor;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test02 
    public static void main(String[] args) 
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        Actor actor =context.getBean(Actor.class);
        actor.sing();
        actor.rap();
        actor.dance();
        actor.baskball();
    

深入理解动态代理

在Java开发中如果一个类中的方法在基本功能之外需要进行功能扩充或者功能增强,如:事务控制、权限判断、日志记录等等操作,此时可以使用动态代理机制。

Java的JDK中Proxy类可以实现基于接口的动态代理,实现步骤示例如下:

1.因为Proxy类必须基于接口进行动态代理,所以首先创建接口,定义接口的规范,即功能方法的定义。

package com.xue.service;

public interface BookService 
    //音乐表演
    void musicAct(int money);
    //舞蹈表演
    void danceAct(int money);
    //娱乐表演
    void yueLeAct(int money);

2.定义实现接口的子类,实现接口定义的方法,此方法只需要把核心功能实现即可,其他增强的操作可以在代理类中实现。

package com.xue.service.Impl;

import com.xue.service.BookService;

public class BookServiceImpl implements BookService 
    @Override
    public void musicAct(int money) 
        System.out.println("音乐表演");
    

    @Override
    public void danceAct(int money) 
        System.out.println("舞蹈表演");
    

    @Override
    public void yueLeAct(int money) 
        System.out.println("娱乐表演");
    

Spring框架基础2

Spring框架基础2

测试Spring的AOP思想和注解的使用

 

导包(在前面的基础上添加)

 

SpringAOP名词解释

AOP编程思想:横向重复代码,纵向抽取;就是说多个地方重复的代码可以抽取出来公用(过滤器等可以体现)
动态代理:动态代理可以体现AOP思想;对目标方法进行增强
SpringAOP开发:封装了动态代理代码(包括cglib代理),可以对任何类进行代理增强
Joinpoint(连接点):目标对象中,所有可以增强的方法
Pointcut(切入点):目标对象,已经增强的方法
Advice(通知/增强):增强的代码
Target(目标对象):被代理对象
Weaving(织入):将通知应用到切入点的过程
Proxy(代理):将通知织入到目标对象之后,形成代理对象
aspect(切面):切入点+通知

 

创建配置文件(如图由上至下)

<?xml version="1.0" encoding="UTF-8"?>
<!-- 使用某个标签之前要导入相应的约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">

    <!-- 配置目标对象 -->
    <bean name="userService" class="com.service.UserServiceImpl"></bean>
    <!-- 配置通知对象 -->
    <bean name="myAdvice" class="com.anno.MyAdvice"></bean>
    <!-- 将通知织入目标对象,利用注解实现 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<!-- 使用某个标签之前要导入相应的约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">

    <!-- 配置目标对象 -->
    <bean name="userServiceTarget" class="com.service.UserServiceImpl"></bean>
    <!-- 配置通知对象 -->
    <bean name="myAdvice" class="com.aspect.MyAdvice"></bean>
    <!-- 将通知织入目标对象 -->
    <aop:config>
        <!-- 配置切入点,即需要加强功能的方法
         public void com.service.UserServiceImpl.save()
         void com.service.UserServiceImpl.save()
         * com.service.UserServiceImpl.save()
         * com.service.UserServiceImpl.*
         * com.service.UserServiceImpl.*(..)
         * com.service.*ServiceImpl.*(..)-->
        <aop:pointcut id="pc" expression="execution(* com.service.*ServiceImpl.*(..))"/>
        <aop:aspect ref="myAdvice">
            <!-- 将myAdvice的before切入到UserServiceImpl.save() -->
            <aop:before method="before" pointcut-ref="pc"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pc"/>
            <aop:around method="around" pointcut-ref="pc"/>
            <aop:after-throwing method="afterException" pointcut-ref="pc"/>
            <aop:after method="after" pointcut-ref="pc"/>
        </aop:aspect>
    </aop:config>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd ">

    <!-- 扫描相应包下的类的所有注解 -->
    <!-- 会扫描该包下的所有子孙类 -->
    <context:component-scan base-package="com.bean"/>

    <bean name="car2" class="com.bean.Car">
        <property name="name" value="玛莎拉蒂"/>
        <property name="color" value="red"/>
    </bean>

</beans>

 

相应的实体类与接口

package com.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author: XDZY
 * @date: 2018/9/5 23:38
 * @description: 车辆实体类
 */
@Component("car")
public class Car {
    @Value("兰博基尼")
    private String name;
    @Value("red")
    private String color;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name=\'" + name + \'\\\'\' +
                ", color=\'" + color + \'\\\'\' +
                \'}\';
    }
}
package com.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

/**
 * @author: XDZY
 * @date: 2018/9/5 19:31
 * @description: 用户实体类
 * 通过注解获取对象
 * "@Component("user")==<bean name="user" class="com.bean.User"/>"
 */
@Component("user")
//@Service("user")指定为service层
//@Controller("user")指定为web层
//@Repository("user")指定为dao层
//单例还是多例
//@Scope(scopeName = "singleton")
public class User {
    //通过反射设置值,破坏了封装性
    @Value("xdzy")
    private String name;
    @Value("15")
    private int age;

    //自动配置car属性;但是有多个对象时,不知道获取哪个
    //@Autowired
    //指定哪个对象
    //@Qualifier("car2")
    //手动注解使用哪个car
    @Resource(name = "car2")
    private Car car;

    //创建对象前调用
    @PostConstruct
    public void init() {
        System.out.println("初始化方法");
    }

    //对象销毁前调用
    @PreDestroy
    public void destroy() {
        System.out.println("销毁方法");
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public String getName() {
        return name;
    }

    //通过set设置值,推荐使用
    @Value("xdzy")
    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name=\'" + name + \'\\\'\' +
                ", age=" + age +
                ", car=" + car +
                \'}\';
    }
}
package com.service;

/**
 * @author: XDZY
 * @date: 2018/9/6 20:21
 * @description:
 */
public interface UserService {
    void save();

    void del();

    void update();

    void find();
}
package com.service;

/**
 * @author: XDZY
 * @date: 2018/9/6 20:22
 * @description:
 */
public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("增加用户");
    }

    @Override
    public void del() {
        System.out.println("删除用户");
    }

    @Override
    public void update() {
        System.out.println("修改用户");
    }

    @Override
    public void find() {
        System.out.println("查询用户");
    }
}

 

JDK动态代理

package com.service;

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

/**
 * @author: XDZY
 * @date: 2018/9/6 20:25
 * @description: JDK动态代理
 * 被代理对象必须要实现接口,才能产生代理对象,如果没有接口将不能使用动态代理技术
 * 动态代理可对方法进行增强,如增加事务的打开与提交
 * 个人理解:它是对service实现类里所有的方法进行了增强;
 * 在不破坏原有结构的情况下,生成动态代理对象,对原有方法进行增强
 */
public class UserServiceProxyFactory implements InvocationHandler {
    private UserService us;

    public UserServiceProxyFactory(UserService us) {
        this.us = us;
    }

    public UserService getUserServiceProxy() {
        //生成动态代理
        UserService userProxy = (UserService) Proxy.newProxyInstance(UserServiceProxyFactory.class.getClassLoader(),
                UserServiceImpl.class.getInterfaces(),
                this);
        //返回一个动态代理对象
        return userProxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("打开事务");
        Object invoke = method.invoke(us, args);
        System.out.println("提交事务");
        return invoke;
    }
}

 

Cglib代理

package com.service;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author: XDZY
 * @date: 2018/9/6 20:25
 * @description: Cglib动态代理
 * 可以对任何类生成代理,对目标对象进行继承代理
 */
public class UserServiceProxyFactory2 implements MethodInterceptor {
    public UserService getUserServiceProxy() {
        //生成代理对象
        Enhancer en = new Enhancer();
        //对谁进行代理
        en.setSuperclass(UserServiceImpl.class);
        //代理要做什么
        en.setCallback(this);
        //创建代理对象
        UserService us = (UserService) en.create();
        return us;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //打开事务
        System.out.println("打开事务");
        //调用原有方法
        Object returnValue = methodProxy.invokeSuper(o, objects);
        //提交事务
        System.out.println("提交事务");
        return returnValue;
    }
}

 

通知类

package com.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
 * @author: XDZY
 * @date: 2018/9/7 08:23
 * @description: 利用注解实现通知
 * "@Aspect:表示该类是一个通知类
 */
@Aspect
public class MyAdvice {
    //方便管理切入点
    @Pointcut("execution(* com.service.*ServiceImpl.*(..))")
    public void pc() {
    }

    //配置通知,并指定织入到哪去
    //前置通知:目标方法运行之前
    @Before("MyAdvice.pc()")
    public void before() {
        System.out.println("前置通知");
    }

    //后置通知(如果出现异常不会调用):之后
    @AfterReturning("execution(* com.service.*ServiceImpl.*(..))")
    public void afterReturning() {
        System.out.println("后置通知(如果出现异常不会调用)");
    }

    //环绕通知:之前之后
    @Around("execution(* com.service.*ServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知之前部分");
        //调用目标方法
        Object proceed = pjp.proceed();
        System.out.println("环绕通知之后部分");
        return proceed;
    }

    //异常拦截通知:出现异常调用
    @AfterThrowing("execution(* com.service.*ServiceImpl.*(..))")
    public void afterException() {
        System.out.println("出现异常调用");
    }

    //后置通知(无论是否出现异常都会调用):之后
    @After("execution(* com.service.*ServiceImpl.*(..))")
    public void after() {
        System.out.println("后置通知(无论是否出现异常都会调用)");
    }
}
package com.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @author: XDZY
 * @date: 2018/9/7 08:23
 * @description: 通知
 * 前置通知:目标方法运行之前
 * 后置通知(如果出现异常不会调用):之后
 * 环绕通知:之前之后
 * 异常拦截通知:出现异常调用
 * 后置通知(无论是否出现异常都会调用):之后
 */
public class MyAdvice {
    //前置通知:目标方法运行之前
    public void before() {
        System.out.println("前置通知");
    }

    //后置通知(如果出现异常不会调用):之后
    public void afterReturning() {
        System.out.println("后置通知(如果出现异常不会调用)");
    }

    //环绕通知:之前之后
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知之前部分");
        //调用目标方法
        Object proceed = pjp.proceed();
        System.out.println("环绕通知之后部分");
        return proceed;
    }

    //异常拦截通知:出现异常调用
    public void afterException() {
        System.out.println("出现异常调用");
    }

    //后置通知(无论是否出现异常都会调用):之后
    public void after() {
        System.out.println("后置通知(无论是否出现异常都会调用)");
    }
}

 

测试类

package com.test;

import com.service.UserService;
import com.service.UserServiceImpl;
import com.service.UserServiceProxyFactory;
import com.service.UserServiceProxyFactory2;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: XDZY
 * @date: 2018/9/6 09:32
 * @description: 测试注解获取对象
 * "@RunWith:可以帮助我们创建容器,这样xml地址改动,测试方法不用全部修改
 * "@ContextConfiguration:指定容器位置
 * 注意junit版本问题
 */
//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:resources/applicationContext.xml")
public class Demo1 {
    /*@Resource(name = "user")
    private User user;

    @Test
    public void test(){
        System.out.println(user);
    }*/

    //JDK动态代理
    @Test
    public void test1() {
        UserService us = new UserServiceImpl();
        UserServiceProxyFactory factory = new UserServiceProxyFactory(us);
        UserService userProxy = factory.getUserServiceProxy();
        userProxy.save();

        //代理对象和被代理对象实现了相同的接口(false)
        System.out.println(userProxy instanceof UserServiceImpl);
    }

    //Cglib动态代理
    @Test
    public void test2() {
        UserServiceProxyFactory2 factory = new UserServiceProxyFactory2();
        UserService userProxy = factory.getUserServiceProxy();
        userProxy.save();

        //代理对象继承了被代理对象(true)
        System.out.println(userProxy instanceof UserServiceImpl);
    }

    //通知织入
    @Test
    public void test() {
        //创建容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("com/aop/applicationContext.xml");
        //获取user对象
        UserService userService = (UserService) ac.getBean("userServiceTarget");
        userService.save();
    }

    //注解通知织入
    @Test
    public void test4() {
        //创建容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("com/anno/applicationContext.xml");
        //获取user对象
        UserService userService = (UserService) ac.getBean("userService");
        userService.save();
    }
}

 

以上是关于Spring框架AOP增强,动态代理的主要内容,如果未能解决你的问题,请参考以下文章

Spring框架基础2

Spring5 AOP 学习笔记

Spring AOP

框架学习之Spring----AOP and jdbcTemplate

Spring框架AOP使用扩展

解析Spring第三天(面向切面AOP)