【JAVA-AOP】

1.什么是AOP

Aspect Oriented Programming 的缩写,意为:面向切面编程

是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2.AOP概念

概念定义:

横切关注点:跨越应用程序多个模块的方法或功能。即,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …

切面(Aspect):横切关注点 被模块化 的特殊对象。即,它是一个类。

通知(Advice)【增强】:切面必须要完成的工作。即,它是类中的一个方法。它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

目标(Target):被通知对象。

代理(Proxy):向目标对象应用通知之后创建的对象。

切入点(PointCut):切面通知 执行的 “地点”的定义。表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

连接点(JointPoint):与切入点匹配的执行点。典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 jointpoint。

织入(Weaving):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

3.AOP底层原理

AOP底层使用动态代理,有两种情况

方法一:有接口情况,使用JDK动态代理;创建接口实现类代理对象、增强类的方法

方法二:没有接口情况,使用CGLIB动态代理;创建子类的代理对象,增强类的方法

4.动态代理

动态代理机制中有两个重要的类和接口InvocationHandler(接口)和Proxy(类),这个类Proxy和接口InvocationHandler是我们实现动态代理的核心。

InvocationHandler接口

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。

查看invoke 方法:

    /**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

Proxy

Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

三个参数:

loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法
h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

5.AOP实现示例

(0)使用JDK动态代理,使用Proxy类里面的方法创建代理对象

调用 newProxyInstance 方法:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

(1)首先我们定义一个接口UserDao 

public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}

(2)创建接口实现类UserDaoImpl ,实现方法add,update  这个类是真实的对象

public class UserDaoImpl implements UserDao{
    @Override
    public int add(int a,int b) {
        return a+b;
    }

    @Override
    public String update(String id) {
        return id;
    }

}

(3)使用Proxy类创建接口代理对象

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

/**
 * 创建代理对象代码
 * InvocationHandler
 */
public class UserDaoProxy implements InvocationHandler {
    /**
     * 创建的是谁的代理对象,把谁传递过来
     * 有参构造传递
     * @param proxy the proxy instance that the method was invoked on
     *
     * @param method the {@code Method} instance corresponding to
     * the interface method invoked on the proxy instance.  The declaring
     * class of the {@code Method} object will be the interface that
     * the method was declared in, which may be a superinterface of the
     * proxy interface that the proxy class inherits the method through.
     *
     * @param args an array of objects containing the values of the
     * arguments passed in the method invocation on the proxy instance,
     * or {@code null} if interface method takes no arguments.
     * Arguments of primitive types are wrapped in instances of the
     * appropriate primitive wrapper class, such as
     * {@code java.lang.Integer} or {@code java.lang.Boolean}.
     *
     * @return
     * @throws Throwable
     */

    private Object obj;
    public UserDaoProxy(Object obj){
        this.obj = obj;
    }

    /**
     * 增强的逻辑
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前 在真实的对象执行之前我们可以添加自己的操作
        System.out.println("方法执行之前..."+method.getName()+ "传递的参数"+ Arrays.toString(args));

        //被增强的方式执行
        Object res = method.invoke(obj,args);
        //方法之后 在真实的对象执行之后我们可以添加自己的操作
        System.out.println("方法执行之后 : "+ obj);

        return res;
    }

}

(4)实现代理对象

//使用 Proxy 类创建接口代理对象
public class JDKProxy {
    public static void main(String[] args) {

        //创建接口实现类代理对象
        Class[] interfaces = {UserDao.class};

        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao  = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));

        int res = dao.add(1, 2);
        System.out.println("result:"+res);
    }
}

6.AOP操作

1. Spring框架一般都是基于AspectJ实现AOP操作

        什么是AspectJ ?

AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作

2. 基于AspectJ实现AOP操作

(1)基于xml配置文件实现

(2)基于注解方式实现(使用)

3. 在项目工程里面引入AOP相关依赖

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.15.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.9</version>
        </dependency>

        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.9</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

4. 切入点表达式

(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强

(2)语法结构:

execution (【权限修饰符】【返回类型】【类全路径】【方法名称】(【参数列表】))

举例1:对com.jin.dao.UserDao类里面的add进行增强

execution(* com.jin.dao.UserDao.add(..))

举例2:对com.jin.dao.UserDao类里面的所有的方法进行增强

execution(* com.jin.dao.UserDao.*(..))

举例3:对com.jin.dao类里面所有类,类里面的所有的方法进行增强

execution(* com.jin.dao.*.*(..))

7.AOP操作(AspectJ注解)【重点】{请进行实操}

1、创建类,在类里面定义方法

//被增强的类
public class User {
    public void add(){
        System.out.println("add ...");
    }

2、创建增强类(编写增强逻辑)

(1)在增强类里面,创建方法,让不同方法代表不同通知类型

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

3、进行通知【增强】的配置

(1)在spring配置文件.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: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.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <!--开启注解扫描-->
<context:component-scan base-package="com.jin.aopanno"></context:component-scan>
</beans>

(2)使用注解创建User和UserProxy对象

//被增强的类
@Component
public class User {
   ...
}

//增强类
@Component
public class UserProxy {
   ...
}

(3)在增强类上面添加注解@Aspect

//增强类
@Component
@Aspect   //生成代理对象
public class UserProxy {
 	...   
}

(4)在spring配置文件.xml中开启生成代理对象

<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4、配置不同类型的通知

(1)在增加类的里面,通知方法上面添加通知类型注解,使用切入点表达式配置

//增强类
@Component
@Aspect   //生成代理对象
public class UserProxy {
    //前置通知
    //@Before注解表示作为前置通知
    @Before(value = "execution(* com.jin.aopanno.User.add(..))")
    public void before(){
        System.out.println("before ...");
    }
    @AfterReturning(value = "execution(* com.jin.aopanno.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning ...");
    }
    @After(value = "execution(* com.jin.aopanno.User.add(..))")
    public void after(){
        System.out.println("after ...");
    }


    @AfterThrowing(value = "execution(* com.jin.aopanno.User.add(..))")
    public void afterThrowing(){
        System.out.println("AfterThrowing ...");
    }
    @Around(value = "execution(* com.jin.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前 ...");
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后 ...");
    }
}

运行截图:

5、相同的切入点抽取

//增强类
@Component
@Aspect   //生成代理对象
public class UserProxy {

	//相同切入点抽取
    @Pointcut(value = "execution(* com.jin.aopanno.User.add(..))")
    public void pointdemo(){}
    
    //前置通知
    //@Before注解表示作为前置通知
    @Before(value = "pointdemo()")
    public void before(){
        System.out.println("before ...");
    }
}    

6、有多个增强类多同一个方法进行增强,设置增强类优先级

(1)在增强类前面添加注解@Order(数字类型值),数字类型值越小优先级越高

@Component
@Aspect   //生成代理对象
@Order(1)  //优先级(数字值越小优先级越高)
public class PersonProxy {
   ...
}

7、完全使用注解开发

(1)创建配置类,不需要创建xml配置文件

@ComponentScan(basePackages = {"com.jin.pojo"})
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) //默认EnableAspectJAutoProxy为false
public class SpringConfig {
}

(2)测试代码

@Test
    public void MyTest01(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = context.getBean("user", User.class);
        user.add();
    }

8.AOP操作(AspectJ配置文件)

1、创建两个类,增强类和被增强类,创建方法

//被增强类
public class Book {
    public void buy(){
        System.out.println("buy ....");
    }
}
//增强类
public class BookProxy {

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

2、在spring配置文件中创建两个类对象

<!--创建对象-->
<bean id="book" class="com.jin.aop.Book"></bean>
<bean id="bookProxy" class="com.jin.aop.BookProxy"></bean>

3、在spring配置文件中配置切入点

<!--配置aop增强-->
<aop:config>
    <!--切入点-->
    <aop:pointcut id="p" expression="execution(* com.jin.aop.Book.buy(..))"/>
    <!--配置切面-->
    <aop:aspect ref="bookProxy">
        <!--增强作用在具体的方法上-->
        <aop:before method="before" pointcut-ref="p"/>
    </aop:aspect>
</aop:config>

4、测试代码

@Test
    public void MyTest(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Book book = context.getBean("book", Book.class);
        book.buy();
    }
拓展

Proxy还有其它的几个方法:

getInvocationHandler:返回指定代理实例的调用处理程序
getProxyClass:给定类加载器和接口数组的代理类的java.lang.Class对象。
isProxyClass:当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。
newProxyInstance:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

Advice 的类型

before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)

after return advice, 在一个 join point 正常返回后执行的 advice

after throwing advice, 当一个 join point 抛出异常后执行的 advice
after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
introduction,introduction可以为原有的对象增加新的属性和方法。

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值