1. 概念
代理(Proxy) 是一种设计模式,提供了对目标对象另外的访问方式:即通过代理对象访问目标对象。这样做的好处是 可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
2. Java 代理(三种)
1)静态代理
静态代理概念:代理类实现与目标对象相同的接口,通过构造器或set方法给代理对象注入目标对象。在实现代理对象接口方法时,内部调用目标对象真正实现方法并且可以添加额外的业务控制,从而实现在不修改目标对象的基础上,对目标对象进行扩展。
2)JDK动态代理
动态代理:代理类在程序运行时创建的代理方式,有以下特点
3)CGLib代理
CGLib代理概念:CGLib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。静态代理和动态代理模式都要求目标对象实现接口,但是有时目标对象只是一个单独的对象,并没有实现任何的接口,这时就可以使用以目标对象子类的方式类实现代理,即CGLib代理。
使用CGLib子类代理:
cglib
的jar文件,但是Spring的核心包中已经包括了Cglib功能1. 什么是AOP?
AOP(Aspect Oriented Programming)
,即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以实现业务逻辑各个部分的隔离,从而使得业务逻辑各个部分的耦合性降低,提高程序的可重用性,同时提高开发效率。简单来说AOP使用动态代理技术,实现在不修改Java源代码的情况下,运行时实现方法功能的增强。
2. 作用及优势
3. 实现原理
原理:动态代理技术
说明:在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式:如果目标对象实现接口,使用JDK代理;目标对象没有实现接口,使用CGLib代理。
4. 常用术语
Target(目标对象)
:被代理的对象;Joinpoint(连接点)
:在Spring中,连接点指的都是方法(指的是那些要被增强功能的候选方法),Spring只支持方法类型的连接点;Pointcut(切入点)
:切入点是指要对哪些 Joinpoint 进行拦截的定义;Aspect(切面)
:切面指的是切入点和通知的结合;Advice(通知/增强)
:通知即拦截到 Joinpoint 之后所要做的事情,通知的类型: 前置通知/后置通知/异常通知/最终通知/环绕通知;Weaving(织入)
:织入指的是增强用于目标对象,创建代理对象的过程,spring采用动态代理织入,AspectJ采用编译期织入和类装载期织入;Proxy(代理)
:一个类被AOP织入增强后,即产生一个结果代理类。5. 关键概念
1)什么是切面类?
切面类简单来看就是重复的代码形成的类就是切面类
2)常见的切面类有哪些?
事务切面类、日志切面类、权限切面类
3)面向切面编程?
面向重复代码编程,重复代码只要写一次,自动调用自动运行
1. 开发阶段
2. 运行阶段
Spring框架监控切入点方法的执行,一旦监控到切入点方法被执行,使用动态代理机制,创建目标对象的代理对象,根据通知类型,在代理对象当前执行方法的对应位置,织入通知功能,完成完整的代码逻辑执行。
3. 需求说明
在业务层方法执行的前后,自动加入日志的输出
4. 代码
1)pom.xml(添加依赖)
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.7version>
dependency>
如果maven无法下载org.aspectj.aspectjweave,项目出现红色感叹号
下载aspectjweave jar
到本地,再把该jar包安装到本地maven仓库,cmd命令 mvn install:install-file -Dfile=C:\Users\F7689048\Desktop\jar\aspectjweaver-1.8.7.jar -DgroupId=org.aspectj -DartifactId=aspectjweaver -Dversion=1.8.7 -Dpackaging=jar
2)applicationContext.xml
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut id="pt" expression="execution(* com.zz.service.impl.UserServiceImpl.*())"/>
<aop:before method="insertLog" pointcut-ref="pt"/>
aop:aspect>
aop:config>
3)测试
在执行目标对象方法之前执行记录日志的操作(因为在applicationContext中配置了前置通知)
<?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">
<!--1.创建service-->
<bean id="userService" class="com.zz.service.impl.UserServiceImpl"/>
<!--2.创建日志切面类-->
<bean id="logger" class="com.zz.utils.Logger"/>
<!--3.Aop配置-->
<!--
3.1 切入点表达式配置
作用:对符合切入点表达式规则的类自动生成代理对象。
a.语法
execution(
modifiers-pattern? 访问修饰符,可选
ret-type-pattern 方法返回值类型 (必须)
declaring-type-pattern?name-pattern(param-pattern) 包名类名?方法名称(方法参数)
throws-pattern?)
a1. 完整写法
execution(public void com.itheima.service.impl.UserServiceImpl.save())
a2. 省略访问修饰符/返回值是任意
execution(* com.itheima.service.impl.UserServiceImpl.save())
a3. 包名用*
execution(* com.*.*.*.UserServiceImpl.save())
a3. 省略子包
com 包及其所有子包下的UserServiceImpl类会生成代理对象
execution(* com..UserServiceImpl.save())
a4. 对com包或子包下的ServiceImpl结尾的类生成代理对象
execution(* com..*ServiceImpl.save())
a5. 方法用*/参数用*
execution(* *()) 对所有的方法所在的类生成代理对象
execution(* *(*)) 参数用*表示参数任意,但必须有参数
execution(* *(..)) 参数用*表示参数任意,参数可有可无
a6. 【最常用写法】
execution(* com.itheima.service.impl.*.*(..))
a7. 拦截所有的save或者update方法
execution(* save(..)) || execution(* update(..))
execution(* save(..)) or execution(* update(..))
a7. 不拦截update方法
!execution(* update(..))
not execution(* update(..)) 注意not前面要有空格
a8. 对IOC容器中所有的Service结尾的类生成代理对象
bean(*Service)
-->
<aop:config>
<!--切面类的配置,通过ref引用切面类(日志工具类)-->
<aop:aspect ref="logger">
<!--配置切入点表达式:对符合切入点表达式规则的类自动生成代理对象-->
<aop:pointcut id="pt" expression="bean(*Service)"/>
<!--【前置通知】再执行目标对象方法之前执行切面类的insertLog方法-->
<aop:before method="insertLog" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
1. applicationContext.xml常用标签
标签 | 作用 |
---|---|
|
声明aop配置 |
|
配置切面 |
|
配置切入点表达式 |
|
配置前置通知 |
|
配置后置通知 |
|
配置异常通知 |
|
配置最终通知 |
|
配置环绕通知 |
2. 通知类型
try{
[前置通知]
执行目标对象方法
[后置通知]
}catch(Exception e){
[异常通知]
}finally{
[最终通知]
}
3. 环绕通知
环绕通知,是Spring框架提供的一种可以手动控制通知执行时间点和顺序的特殊通知类型,它使用起来更加灵活。
applicationContext.xml
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut id="pt" expression="bean(*Service)" />
<aop:around method="around" pointcut-ref="pt" />
aop:aspect>
aop:config>
logger.java
/**
* 环绕通知,环绕目标方法前后执行 推荐使用环绕通知,更好的地方:
* 1.可以修改方法参数
* 2.可以修改方法返回结果
* 3.获取当前执行的方法、类的信息
*/
public Object around(ProceedingJoinPoint pjp) {
// 获取方法参数
Object[] args = pjp.getArgs();
System.out.println("获取目标对象:" + pjp.getTarget().getClass().getName());
System.out.println("获取当前执行的方法名称:" + pjp.getSignature().getName());
try {
System.out.println("【环绕前】");
// 执行目标对象方法
Object retV = pjp.proceed(args);
System.out.println("【环绕后】");
return retV;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("【环绕异常】");
return null;
} finally {
System.out.println("【环绕最终】");
}
}
1.logger.java
@Component // 创建切面类对象
@Aspect // 指定当前类为切面类
public class Logger {
// 统一定义切入点表达式
@Pointcut("execution(* com.zz..*ServiceImpl.*(..))")
public void pt(){}
/**
* 环绕通知,环绕目标方法前后执行
* 推荐使用环绕通知,更好的地方:
* 1.可以修改方法参数
* 2.可以修改方法返回结果
* 3.获取当前执行的方法、类的信息
*/
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
// 获取方法参数
Object[] args = pjp.getArgs();
System.out.println("获取目标对象:" + pjp.getTarget().getClass().getName());
System.out.println("获取当前执行的方法名称:" + pjp.getSignature().getName());
try {
System.out.println("【环绕前】");
// 执行目标对象方法
Object retV = pjp.proceed(args);
System.out.println("【环绕后】");
return retV ;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("【环绕异常】");
return null;
} finally {
System.out.println("【环绕最终】");
}
}
2.applicationContext.xml
<context:component-scan base-package="com.zz"/>
<aop:aspectj-autoproxy/>