SpringAOP

SpringAOP

1.代理模式

为某一个对象(委托类)提供一个代理(代理类),用来控制对这个对象的访问。
委托类和代理类有一个共同的父类或者父接口。
代理类会对请求做预处理、过滤,将请求分配给指定对象。

生活中常见的代理模式:
租房中介、婚庆公司等
代理模式的两个设计原则:
1.代理类与委托类具有相似的行为
2.代理类能够增强委托类的行为

常见的代理模式:
1.静态代理
2.动态代理

2.静态代理

2.1 代理的三要素(结婚为例)

a.有共同的行为(结婚) -定义接口
b.目标角色/真实角色(新人)-实现接口
c.代理角色(婚庆公司) -实现接口 增强用户行为

2.2静态代理的特点

1.目标角色固定
2.在应用程序之前就得知目标角色
3.代理对象会增强用户行为
4.有可能存在多个代理,产生“类爆炸”(缺点)

2.3静态代理的实现

a.有共同的行为(结婚) -定义接口

package com.xxxx.Proxy;

/**
* 定义接口-定义行为
*/
public interface Marry {
public void toMarry();
}

b.目标角色/真实角色(新人)-实现接口

package com.xxxx.Proxy;

/**
* 静态代理 ->目标角色
* 实现行为
*/
public class You implements Marry {
@Override
public void toMarry() {
System.out.println("我要结婚了...");
}
}

c.代理角色(婚庆公司) -实现接口 增强用户行为

package com.xxxx.Proxy;

/**
* 静态代理-代理角色
* 1.实现行为
* 2.增强行为
*/
public class MarryCompanyProxy implements Marry {
//目标对象
private Marry target;
//通过带参构造传递目标对象
public MarryCompanyProxy(Marry target) {
this.target = target;
}

//实现行为
@Override
public void toMarry() {
//用户行为增强
before();
//调用目标对象中的方法
target.toMarry();
//用户行为增强
after();
}

/**
* 用户行为增强
*/
private void after() {
System.out.println("新婚快乐...");
}

private void before() {
System.out.println("婚礼现场正在布置中...");
}
}

运行代码

同理,可写出以租房中介代理

package com.xxxx.Proxy;

public interface RentHose {
public void toRentHose();
}
package com.xxxx.Proxy;

public class Owner implements RentHose {
@Override
public void toRentHose() {
System.out.println("两室一厅,月租五千");
}
}
package com.xxxx.Proxy;

public class AgentProxy implements RentHose {
//目标对象
private RentHose target;
//通过带参构造器获取目标对象

public AgentProxy(RentHose target) {
this.target = target;
}

//实现行为
@Override
public void toRentHose() {
target.toRentHose();
//用户增强行为
System.out.println("房型朝北,采光好");
System.out.println("价格可议");
}
}

3.动态代理

相对于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由java反射机制动态产生,它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了变成工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,既满足生产需要同时又达到代码通用的目的。

可以根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象

动态代理的两种实现方式

  1.JDK动态代理
  2.CGLIB动态代理
  动态代理的特点:
  1.目标对象不固定
  2.在程序运行时,动态创建目标对象
  3.代理对象会增强目标对象的行为

3.1JDK动态代理

JDK动态代理的实现
注:JDK动态代理的目标对象必须有接口实现

package com.xxxx.Proxy;

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

/**
* jdk动态代理类
* 每一个代理类都需要实现InvocationHandler接口
*/
public class jdkHandler implements InvocationHandler {
//目标对象
private Object target;//目标对象的类型不固定,创建时动态生成
//通过带参构造器传递目标对象

public jdkHandler(Object target) {
this.target = target;
}

/**
* 1.调用目标对象的方法(返回object)
* 2.增强目标对象的行为
* @param proxy 调用该方法的代理实例
* @param method 目标对象的方法
* @param args 目标对象的方法所需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//用户的增强行为
System.out.println("=============方法调用前");
//调用目标对象的方法(返回object)
Object object=method.invoke(target,args);
//用户的增强行为
System.out.println("方法调用后=============");
return object;

}
/**
* 获取代理对象
* public static Object newProxyInstance(ClassLoader Loader,
* Class[] interfaces,
* InvocationHandler h)
* Loader: 类加载器
* interfaces:接口数组
* getClass().getInterfaces():目标对象的接口数组
* h:InvocationHandler接口(传入InvocationHandler接口的实现类)
* @return
*/
public Object getProxy(){
Object object = Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
return object;
}


}

测试代码

package com.xxxx.Proxy;

public class jdkHandlerTest {
public static void main(String[] args) {
//目标对象
You you=new You();
//得到代理类
jdkHandler jdkHandler=new jdkHandler(you);
//得到代理对象
Marry marry= (Marry) jdkHandler.getProxy();
//通过代理对象调用目标对象的方法
marry.toMarry();
Owner owner=new Owner();
jdkHandler jdkHandler02=new jdkHandler(owner);
RentHose rentHose= (RentHose) jdkHandler02.getProxy();
rentHose.toRentHose();
}
}

测试结果

总结:动态代理目标角色不固定,(既可以做结婚操作也可以做租房操作)取决于给定的目标。

3.2CGLIB动态代理

jdk的动态代理机制只能代理实现了接口的类,而不能实现接口的类不能使用ojdk的动态代理,cglib是针对类来实现代理的,它的原理是对只懂得目标类生成一个子类,并覆盖其中的方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

cglib动态代理的实现

1.导入cglib依赖坐标
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
2.定义类实现MethodInterceptor方法
i.获取代理对象 (getProxy方法),通过Enhance对象中的create()方法生成一个类,用于生成代理对象
ii.设置父类,(通过构造器获取目标对象,将目标类作为代理类的父类)
iii.设置拦截器 回调对象本身对象,生成代理对象并返回给调用者
iv重写方法
package com.xxxx.Proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibInterceptor implements MethodInterceptor {
//目标对象
private Object target;
//通过构造器传入目标对象

public CglibInterceptor(Object target) {
this.target = target;
}

/**
* 获取代理对象
* @return
*/
public Object getProxy(){
//通过Enhance对象中的create()方法生成一个类,用于生成代理对象
Enhancer enhancer =new Enhancer();
//设置父类,(将目标类作为代理类的父类)
enhancer.setSuperclass(target.getClass());
//设置拦截器 回调对象本身对象
enhancer.setCallback(this);
//生成代理对象并返回给调用者
return enhancer.create();
}

/**
* 拦截器
* 1.目标对象的方法调用
* 2.增强行为
* @param o cglib动态生成的代理类的实例
* @param method 实体类所调用的被代理的方法的引用
* @param objects 参数列表
* @param methodProxy 生成的代理类对方法的代理引用
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//增强行为
System.out.println("===========方法调用前");
//调用目标对象类中的方法
Object object=methodProxy.invoke(target,objects);
//增强行为
System.out.println("方法调用后===========");
return object;
}
}
package com.xxxx.Proxy;

public class User {
public void Test(){
System.out.println("用户....");
}
}
package com.xxxx.Proxy;

public class CglibInterceptroTest {
public static void main(String[] args) {
/* 通过cglib动态代理实现:没有接口的实现类*/

//得到目标对象
User user=new User();
//得到拦截器
CglibInterceptor cglibInterceptor=new CglibInterceptor(user);
//得到代理对象
User u = (User) cglibInterceptor.getProxy();
u.Test();
}
}

总结:cglib动态代理的实现原理是继承思想,代理类继承目标类,重写目标类中的方法

3.3JDK与CGLIB的区别

  • jdk动态代理实现接口,cglib动态代理继承思想
  • jdk动态代理(目标对象存在接口时)执行效率高于cglib
  • 如果目标对象有接口实现,选择jdk代理,如果没有接口实现选择cglib代理

4.spring AOP

4.1spring AOP日志处理带来的问题

需要对以下功能进行日志记录,事务处理

假如没有AOP,在做日志和事物处理的时候,我们会在每个方法中添加相关处理

但大多数的代码是相同的,为了实现代码的复用,我们可能把日志和事务处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法。

但这样两个方法就是强耦合的,假如此时我们不需要这个功能了,或者想换成其他功能,那么就必须一个个修改。
通过代理模式,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想,被称为面向切面编程,即AOP。

4.2什么是AOP

Aspect Orientd Programing 面向切面编程,相比较oop面向对象编程来说,Aop关注的不再是程序代码中某个类,某些方法,而aop考虑的更多的是一种面到面的切入,即层与层之间的一种切入。所以称之为切面。联想大家吃的汉堡(中间夹肉)。那么oop是怎么做到拦截整个面的功能呢?考虑前面学到的servlet filter的配置,实际上也是aop的实现。

4.3AOP能做什么?

AOP主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用哦。

4.4AOP的特点

1.降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)
2.提高代码的复用性
3.提高系统的扩展性。(高版本兼容低版本)
4.可以在不影响原有的功能基础上添加新的功能

4.4.1AOP的底层实现

动态代理(JDK+CGLIB)

4.5AOP基本概念

4.5.1Joinpoint(连接点)

被拦截到的每一个点,spring终止被拦截到的每一个方法,spring aop一个连接点即代表一个方法的执行。

4.5.2Pointcut(切入点)

对连接点警醒拦截的定义(匹配规则定义 规定拦截哪些方法,对那些方法进行处理),spring有专门的表达语言定义。

4.5.3Adive(通知)

拦截到每一个连接点即(每一个方法)后所要做的操作
1.前置通知 (前置增强)–before()执行方法前通知
2.返回通知(返回增强)–afterReturn 方法正常结束返回后的通知
3.异常抛出通知(异常抛出增强)–afterThrow()
4.最终通知–after无论方法是否发生异常,均会执行该通知
5.环绕通知–around保卫一个连接点(join point)的通知,如方法方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或者抛出异常来借宿执行。

4.5.4Aspect(切面)

切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。

4.5.5Target(目标对象)

被代理的目标对象

4.5.6Weave(织入)

将切面应用到目标对象并生成代理对象的这个过程即为织入

4.5.7Introduction(引入)
在不修改原有应用程序代码的情况下,在程序运行期为类动态添加方法或者字段的过程称为引入

5.SpringAop的实现

5.1SpringAop环境的搭建

1.坐标依赖引入

<!--springAOP环境依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.20</version>
</dependency>

2.添加spring.xml的配置
添加命名空间

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

5.2SpringAop的注解实现

5.2.1定义切面

package com.xxxx.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
* 切面
* 切入点和通知的抽象
* 定义 切入点 和 通知
* 切入点:定义要拦截哪些类的哪些方法
* 通知:定义了拦截之后方法要做什么
*/

@Component //将对象交给IOC容器进行实例化
@Aspect //声明当前类是一个切面
public class LogCut {

/**
* 切入点
* 定义要拦截哪些类的哪些方法
* 匹配规则 拦截什么方法
* 定义切入点
* @pointcut("匹配规则")
* Aop切入点表达式
* 1.执行所有的公共方法
* execution(public *(..))
* 2.执行任意的set方法
* execution( *set*(..))
* 3.设置指定包下的任意类的任意方法(指定包:com.xxxx.service)
* exection(* com.xxxx.service.*.*(..))
* 3.设置指定包及子包下的任意类的任意方法(指定包:com.xxxx.service)
* * exection(* com.xxxx.service..*.*(..))
*
* 表达式中的第一个*
* 代表的是方法的修饰范围(public private protected)
* 如果取值是*,则表示所有范围
*
*/
@Pointcut("execution(* com.xxxx.service..*.*(..))")
public void cut(){

}

/**
* 声明前置通知,并将通知应用到指定的切入点上
* 目标类的方法执行前,执行该通知
*/
@Before(value = "cut()")
public void before(){
System.out.println("前置通知...");
}

/**
* 声明返回通知,并将通知应用到指定的切入点上
* 目标类的方法在无异常执行后,执行通知
*/
@AfterReturning(value = "cut()")
public void afterReturn(){
System.out.println("返回通知...");
}

/**
* 声明最终通知,并将通知应用到指定的切入点上
* 目标类的方法在执行后,执行通知(有异常和无异常最终都会执行)
*/
@After(value = "cut()")
public void after(){
System.out.println("最终通知...");
}

/**
* 声明异常通知,并将通知应用到指定的切入点上
* 目标类的方法异常时,执行通知
*/
@AfterThrowing(value = "cut()")
public void afterThrow(){
System.out.println("异常通知...");
}

/**
* 声明环绕通知,并将通知应用到指定的切入点上
* 目标类的方法执行前后,都可通过环绕通知定义响应处理
* 需要通过显示调用的方法,否则无方法访问指定方法pjp.proceed();
* @param pjp
* @return
*/
@Around(value = "cut()")
public Object around(ProceedingJoinPoint pjp){
System.out.println("环绕通知-前置通知...");

Object object=null;
try {
//显式调用对应的方法
object=pjp.proceed();
System.out.println(pjp.getTarget());
System.out.println("环绕通知-返回通知...");

}catch (Throwable throwable){
throwable.printStackTrace();
System.out.println("环绕通知-异常通知...");
}
System.out.println("环绕通知-最终通知...");
return object;
}
}

开启AOP代理

<!--配置AOP代理-->
<aop:aspectj-autoproxy/>

5.3XML实现

将切面中的关于切面的注释去掉,在spring.xml中手动配置相应的配置文件

<!--aop相关配置-->
<aop:config>
<!--aop切面-->
<aop:aspect ref="logCut02">
<!--定义aop切入点-->
<aop:pointcut id="cut" expression="execution(* com.xxxx.service..*.*(..))"/>
<!--配置前置通知,设置前置通知对应的方法名及切入点-->
<aop:before method="before" pointcut-ref="cut"/>
<!--配置返回通知,设置返回通知对应的方法名及切入点-->
<aop:after-returning method="afterReturn" pointcut-ref="cut"/>
<!--配置最终通知,设置最终通知对应的方法名及切入点-->
<aop:after method="after" pointcut-ref="cut"/>
<!--配置异常通知,设置异常通知对应的方法名及切入点-->
<aop:after-throwing method="afterThrow" pointcut-ref="cut"/>
<!--配置环绕通知,设置环绕通知对应的方法名及切入点-->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>

6.SpringAOP总结

6.1代理模式实现三要素

1.接口定义
2.目标对象 与代理对象必须实现统一接口
3.代理对象持有目标对象的引用 增强目标对象的行为

6.2代理模式实现分类以及对应区别

1.静态代理:手动为目标对象制作代理对象,即在程序编译阶段完成代理对象的创建
2.动态代理:在程序运行期动态代理创建目标对象对应代理对象
3.JDK动态代理:被代理目标对象必须实现某一或某一组接口 实现方式 通过回调创建代理对象
4.cglib动态代理:被代理目标对象可以不必实现接口,继承的方式实现。

6.3Aop理解

1.面向切面,相比oop关注的是代码中的层或面
2.解耦,提高系统扩展性
3.提高代码复用性

6.4AOP关键词

1.连接点:每一个方法
2.切入点:匹配的方法集合
3.切面:连接点与切入点的集合决定了切面,横切关注点的抽象
4.通知:前置通知 返回通知 最终通知 异常通知 环绕通知
5.目标对象:被代理的对象
6.织入:程序运行期将切面应用到目标对象 并生成代理对象的过程
7.引入:在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值