spring-aop笔记
2、Spring-AOP
2.1、代理【Proxy】
-
动态代理:
-
特点:字节码谁用谁创建,谁用谁加载
-
作用:不修改源码的基础上对方法增强
-
分类:
-
基于接口的动态代理
-
基于子类的动态代理
-
-
基于接口的动态代理
-
涉及的类:Proxy
-
提供者:JDK官方
-
-
如何创建代理对象:
-
使用Proxy类中的newProxyInstance方法
-
-
创建代理对象的要求:
-
被代理类最少实现一个接口,如果没有则不能使用
-
-
newProxyInstance方法的参数:
-
ClassLoader:类加载器
-
它是用于加载代理对象字节码的.和被代理对象使用相同的类加载器。固定写法。
-
-
Class[]
-
它是用于让代理对象和被代理对象有相同方法。固定写法。
-
-
InvocationHandler
-
它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
-
此接口的实现类都是谁用谁写
-
-
-
来测试一下:
2.1.1、未使用代理
1.定义一个接口,要被代理的接口:Producer【提供者】
//生产者
public interface Producer {
//销售
public void saleProduct(float money);
//售后
public void afterService(float money);
}
2.定义一个类,ProducerImpl【提供者】(要被代理的角色)
//生产者
public class ProducerImpl implements Producer {
//销售
public void saleProduct(float money){
System.out.println("销售产品,拿到钱:" + money);
}
//售后
public void afterService(float money){
System.out.println("售后服务,拿到钱:" + money);
}
}
3.定义一个类,Client【消费者】(未使用代理,直接面向厂家)
//模拟一个消费者(未使用代理)
public class Client {
public static void main(String[] args){
IProducer producer = new Producer();
producer.saleProduct(1000f);
}
}
结果:
2.1.2、使用了代理
1.定义一个类,ProxyMan【代理人】(不收中介费的时候)
public class ProxyMan implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader() ,target.getClass().getInterfaces() ,this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target ,args);
}
}
2.定义一个类,Client【消费者】(使用了代理)
//模拟一个消费者
public class Client {
public static void main(String[] args){
IProducer producer = new Producer();
// producer.saleProduct(1000f);
//代理模式
ProxyMan pm = new ProxyMan();
pm.setTarget(producer);
IProducer producerPoxy = (IProducer)pm.getProxy();
producerPoxy.afterService(1000f);
}
}
结果:
3.当代理人收代理费时(抽取两成):
public class ProxyMan implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader() ,target.getClass().getInterfaces() ,this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value = null;
Float money = (Float) args[0];
if("saleProduct".equals(method.getName())) {
value = method.invoke(target, money * 0.8f);
}
if("afterService".equals(method.getName())){
value = method.invoke(target ,args);
}
return value;
}
}
结果:
被成功抽取两成!
在不改变原有代码的时候,改变传输的信息。不仅仅可以改变值,还可以增加其他东西,代理模式大多数情况用在增加日志等功能。
2.2、Spring中的AOP
2.2.1、AOP相关术语:
-
Joinpoint(连接点):
-
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
-
-
Pointout(切入点):
-
所谓切入点是指我们要对那些
Joinpoint进行拦截的定义。
-
-
Advice(通知/增强)
-
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
-
通知类型:前置通知,后置通知,异常通知,环绕通知。
-
-
Introduction(引介):
-
引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field。
-
-
Target(目标对象)
-
代理的目标对象(也就是说被代理的对象)
-
-
Weaving(织入):
-
是指把增强应用到目标对象来创建新的代理对象的过程。
-
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
-
-
Proxy(代理):
-
一个类被AOP织入增强后,就产生一个结果代理类。
-
-
Aspect(切面):
-
是切入点和通知(引介)的结合。
-
2.2.2、AOP的配置内容
spring中基于XML的AOP配置步骤:
-
把通知Bean也较给spring来管理
<!-- 配置Logger类 -->
<bean id="logger" class="com.gzk.utils.Logger"></bean> -
使用aop:config标签表明开始AOP的配置
-
使用aop:aspect标签表明配置切面
-
id属性:是给切面提供一个唯一标识
-
ref属性:是指定通知类bean的ID
-
-
在aop:aspect标签的内部使用对应标签来配置通知的类型
-
我们现在示例是让pringLog方法在切入点方法执行之前:所以是前置通知
-
aop:before:表示配置前置通知
-
method属性:用于指定Logger类中哪个方法是前置通知
-
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
-
-
aop:after-returning:表示配置后置通知
-
method属性:用于指定Logger类中哪个方法是后置通知
-
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
-
-
aop:after-throwing:表示配置异常通知
-
method属性:用于指定Logger类中哪个方法是异常通知
-
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
-
-
aop:after:表示配置最终通知
-
method属性:用于指定Logger类中哪个方法是最终通知
-
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
-
-
切入点表达式的写法:
-
关键字:execution(表达式)
-
表达式:
-
访问修饰符 返回值 包名.包名...类名.方法名(参数列表)
-
-
标准表达式写法(例):
-
public void com.gzk.servicce.UserServiceImpl.test()
-
-
访问修饰符可以省略
-
void com.gzk.servicce.UserServiceImpl.test()
-
-
返回值可以使用通配符,表示任意返回值
-
* com.gzk.servicce.UserServiceImpl.test()
-
-
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个
*。-
* *.*.*.UserServiceImpl.test()
-
-
包名可以使用
..表示当前包及其子包-
* *..UserServiceImpl.test()
-
-
类名和方法名都可以使用
*来实现通配-
* *..*.*()
-
-
参数列表:
-
可以直接写数据类型:
-
基本类型直接写名称:int(例)
-
* *..*.*(int)
-
-
引用类型写包名.类名的方式:java.lang.String(例)
-
* *..*.*(java.lang.String)
-
-
-
可以使用通配符
*表示任意类型,但是必须有参数-
* *..*.*(*)
-
-
可以使用
..表示有无参数均可,有参数可以使任意类型-
* *..*.*(..)
-
-
-
全通配写法:
-
* *..*.*(..)
-
-
实际开发中,很少用全通配写法,因为会全部被代理,切入点表达式的通常写法:
-
切到业务层或者要被代理的层(或包)实现类下的所有方法:
-
* com.gzk.service.*.*(..)
-
-
-
-
2.2.3、测试(非注解)
测试四种常用通知类型【前置通知,后置通知,异常通知,最终通知】
1.随意配置mapper和service包下的类,用作测试
public interface UserMapper {
void test();
}
public class UserMapperImpl implements UserMapper {
public void test() {
System.out.println("测试中。。。");
}
}
public interface UserService {
void test();
}
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
public void test() {
int i = 1/0;
userMapper.test();
}
}
2.将UserServiceImpl设为被代理的类
3.新建一个utils包,创建一个Logger类【代理类】
public class Logger {
public void beforeLogger(){
System.out.println("前置通知");
}
public void afterReturningLogger(){
System.out.println("后置通知");
}
public void afterThrowingLogger(){
System.out.println("异常通知");
}
public void afterLogger(){
System.out.println("最终通知");
}
}
4.配置xml文件
5.测试:
public class UserTest {
6.结果
当发生异常时:
2.2.4、通用化切入点配置
2.2.5、环绕通知
1.将其他通知注释掉,写一个环绕通知:
<aop:around method="aroundLogger" pointcut-ref="point-cut"/>
2.在Logger类中加入环绕方法:
public void aroundLogger(){
System.out.println("环绕通知");
}
3.结果:
很明显出问题了,被代理类中的方法没了,是什么问题呢?
-
问题:
-
当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
-
-
分析:
-
通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码没有。
-
-
解决:
-
spring框架为我们提供了一个接口,ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
-
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供接口的实现类供我们调用。
-
好了,问题找到且分析了,那我们开始写代码吧
xml配置文件不用变,但Logger代理类中的aroundLogger()方法就要改变。
//环绕通知
public Object aroundLogger(ProceedingJoinPoint point){
Object[] args = point.getArgs();
Object proceed;
try {
//前置通知
System.out.println("前置通知");
proceed = point.proceed(args);
//后置通知
System.out.println("后置通知");
return proceed;
}catch (Throwable e){
//异常通知
System.out.println("异常通知");
throw new RuntimeException(e);
}finally {
//最终通知
System.out.println("最终通知");
}
}
结果:
2.2.6、测试(注解)
1.通过注解方式随意配置mapper和service包下的类,用作测试
public interface UserMapper {
void test();
}
public interface UserService {
void test();
}
2.将UserServiceImpl设为被代理的类
3.新建一个utils包,创建一个Logger类【代理类】(使用注解)
4.此时有两种方式来配置 xml 文件,一种是xml,一种是java
4.1、xml为配置文件
4.2、java为配置文件,创建一个config包,在下面新建一个类
5.测试
5.1、使用xml为配置类测试:
public class UserTest {
5.2、使用java为配置类测试
public class UserTest {
6.结果:

浙公网安备 33010602011771号