1、动态代理(实现方式)
(1)jdk动态代理,使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。
jdk动态代理要求目标类必须实现接口
(2)cglib动态代理:第三方的工具库创建代理对象,原理是继承。
通过继承目标类,创建子类。子类就是代理对象。要求目标类不能是final的(类不能继承),方法也不能是final的(方法不能重写)
2、(1)当我们业务层使用实现接口的方式时,会默认采用 JDK 的动态代理形式;
(2)当我们业务层没有使用实现接口的方式时,会默认采用cglib 的动态代理(这个的执行效率会高一些);
(3)那我们可以既实现接口又想使用 cglib 的动态代理形式的吗?
答案是可以,在xml文件中添加以下配置并在pom.xml中引入依赖(强制使用 cglib 动态代理):
<aop:config proxy-target-class="true"></aop:config>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
这里有一个报错,就是要理解以上一点,不然可能会报这样的错
3、动态代理的作用:
(1)在目标类源代码不改变的情况下,增加功能。
(2)减少代码的重复
(3)专注业务逻辑代码
(4)解耦合,让你的业务功能和日志、事务等非业务功能分离。
1、AOP (Aspect Orient Programning) 面向切面编程
(1)基于动态代理的,可以使用jdk、cglib两种代理方式。
AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。
(2)Aspect:切面,给你的目标类增加的功能,就是切面。像用的日志,事务都是切面;
切面的特点:一般都是非业务方法,独立使用的;
Orient:面向(对着);
Programming:编程。
oop:面向对象编程
2、怎么理解面向切面编程?
(1)需要在分析项目功能时,找出切面;
(2)合理的安排切面的执行时间(在目标方法前,还是目标方法后);
(3)合理的安排切面执行的位置(在哪个类,哪个方法增加增强功能)。
3、术语:
(1)Aspect:切面,表示增强的功能,就是一堆代码,完成某个一个功能。非业务功能;
常见的切面功能有日志、事务、统计信息、参数检查、权限验证;
(2) JoinPoint:连接点,连接业务方法和切面的位置。就某类中 的业务方法;
(3) Pointcut :切入点,指多个连接点方法的集合。多个方法;
(4)目标对象:给哪个类的方法增 加功能,这个类就是 目标对象;
(5)Advice:通知,通知表示切面功能执行的时间。
4、说一个切面有三个关键的要素:
(1)切面的功能代码,切面干什么?就是对原有功能的增强,实现解耦合;
(2)切面的执行位置,使用Pointcut表示切面执行的位置;
(3)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。
5、AOP的实现
aop是一个规范,是动态代理的一个规范化也是一个标准
aop的技术实现框架:
(1)spring: spring在内部实现了aop规范,能做aop的工作。
spring主要在事务处理时使用aop,我们项目开发中很少使用spring的aop实现。因为spring的aop比较笨重。
(2)AspectJ:一个开源的专门做aop的框架。spring框架中集成了AspectJ框架,通过spring就能使用AspectJ的功能。
1、AspectJ框架实现aop有两种方式:
(1)使用xml的配置文件:配置全局事务
(2)使用注解(推荐使用,开发中常用),我们在项目中要做aop功能,一般都使用注解,AspectJ有5个注解。
2、AspectJ框架的便用
切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强)
在AspectJ框架中使用注解表示的(也可以使用xml配置文件中的标签)
AspectJ有5个注解(演示示例中都有的)
(1) @Before:前置通知(增强方法之前执行)
(2) @AfterReturning:后置通知(返回通知)(处理返回值,在方法及返回值之后执行)
(3) @Around:环绕通知(最先执行,功能最强大,通常用于事务,有固定的参数,相当于动态代理中的Method方法)
(4) @AfterThrowing:异常通知(只有在代码出现异常的时候,才会执行)
(5) @After:最终通知(相当于finally)
(6) @Pointcut:定义和管理切入点,可以实现复用(相当于是给对应的切入点起别名)
执行顺序:环绕通知调用方法前的代码 > 前置通知 > 被增强方法 > 后置通知(返回通知) > 最终通知 > 环绕通知调用方法之后的代码,这个不刻意去记忆(你经常用就会记住,但是我用了很久才算清晰的记住了)
下面我就来说一下:使用AspectJ框架实现aop
使用aop:目的是给已经存在的一些类和方法,增加额外的功能。前提是不改变原来的类的代码。
3、使用aspectj实现aop的基本步骤:
(1)新建maven项目
(2)加入依赖
① spring依赖
② aspectj依赖
③junit单元测试
<!--JUint单元测试(非必须)-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
(3)创建目标类:接口和他的实现类。要做的是给类中的方法增加功能
(4)创建切面类:普通类(主要步骤)
①在类的上面加入@Aspect注解,标识这是一个代理类。
②在类中定义方法,方法就是切面要执行的功能代码,在增强方法的上面加入aspectj中的通知注解, 例如@Before前置通知,需要指定切入点表达式execution( )
(5)创建spring的配置文件:声明对象,把对象交给容器统一管理声明对象你可以使用注解或者xml配置文件bean标签(主要步骤)
①声明目标对象
②声明切面类对象
③声明aspectj框架中的自动代理生成器标签。自动代理生成器:用来完成代理对象的自动创建功能的。
(6)创建测试类,从spring容器中获取目标对象(实际就是代理对象)。通过代理执行方法, 实现aop的功能增强。
4、其中还有切入点表达式:就是标识是对哪些类的哪些方法进行增强
格式:execution(访问权限 、方法返回值、方法声明(参数)、异常类型)
1、目录结构:
2、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:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/util http://www.springframework.org/schema/util/spring-util.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
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启组件扫描(全包扫描的方式)-->
<context:component-scan base-package="com.AspectJ"></context:component-scan>
<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<aop:config proxy-target-class="true"></aop:config>
</beans>
3、UserService接口
package com.AspectJ.service;
public interface UserService {
void test();
}
4、UserServiceImpl实现类
package com.AspectJ.service;
import org.springframework.stereotype.Component;
@Component("userServiceImpl")
public class UserServiceImpl implements UserService {
public void test() {
System.out.println("user...");
}
}
5、UserProxy代理类
package com.AspectJ.Proxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//增强的类 (1、2、3、4、5表示通知执行的先后顺序)
@Component//实现类的对象
@Aspect //生成代理对象
public class UserProxy {
//相同切入点的抽取(之后可以直接调用这个方法在相应的表达式中,例如以下的后置通知和环绕通知)
@Pointcut(value = "execution(* com.AspectJ.service.UserServiceImpl.test(..))")
public void pointDemo() {
//这里无需任何代码
}
//2、前置通知:在User类里的方法执行之前先执行
@Before(value = "execution(* com.AspectJ.service.UserServiceImpl.test(..))")//切入点表达式
public void before() {
System.out.println("before...");
}
//4、最终通知:在方法之后执行 (相当于finally)
@After(value = "execution(* com.AspectJ.service.UserServiceImpl.test(..))")
public void after() {
System.out.println("after...");
}
//3、后置通知(返回通知):在方法之后并且在返回值之后执行,处理返回值
@AfterReturning(value = "pointDemo()")
public void afterReturning() {
System.out.println("afterReturning...");
//int a = 10 / 0;
//System.out.println(a);
}
//5、异常通知(只有在代码出现异常的时候,才会执行)
@AfterThrowing(value = "execution(* com.AspectJ.service.UserServiceImpl.test(..))")
public void afterThrowing() {
System.out.println("afterThrowing...");
}
//1、环绕通知(最先执行,功能最强大,通常用于事务,有固定的参数,相当于动态代理中的Method方法)
@Around(value = "pointDemo()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前执行");
//被增强的方法执行
proceedingJoinPoint.proceed();
//一以下的代码最后执行
System.out.println("环绕之后执行");
}
}
6、UserServiceTest测试类
import com.AspectJ.service.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserServiceTest {
@Test
public void testUserService() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
userServiceImpl.test();
}
}