Spring AOP
zzj
一、静态代理
1.1 代理的三要素
- 有共同的行为
- 目标对象
- 代理对象
1.2 静态代理的特点
- 目标对象固定
- 在应用程序执行前就得到目标对象
- 代理对象会增强目标对象的行为
- 可能存在多个代理,引起类爆炸
1.3 静态代理的实现
1.3.1 定义具有共同行为的接口
public interface Marry {
public void toMarry();
}
1.3.2 目标对象
public class You implements Marry{
@Override
public void toMarry() {
System.out.println("你要结婚了...");
}
}
1.3.3 代理对象
public class MarryCompany implements Marry{
//目标对象
private Marry target;
public MarryCompany(Marry target) {
this.target = target;
}
@Override
public void toMarry() {
// 行为增强
System.out.println("婚礼场景布置中...");
// 调用目标对象的方法
target.toMarry();
// 行为增强
System.out.println("进洞房了...");
}
}
1.3.4 通过代理对象加强目标对象的功能
public class StaticProxyTest {
public static void main(String[] args) {
You you=new You();
MarryCompany marryCompany=new MarryCompany(you);
marryCompany.toMarry();
}
}
二、动态代理
由java反射机制动态生成。动态代理的两种实现方式:JDK动态代理,CGLIB动态代理。
2.1 动态代理特点
- 目标对象不固定
- 在应用程序执行时动态创建目标对象
- 代理对象会增加目标对象的行为
2.2 JDK动态代理
注:JDK动态代理的目标对象必须有接口实现
通过Proxy类中的newProxyInstance方法,得到代理对象,返回Object类型。
2.2.1 目标对象接口
public interface UserAction {
public void toRentHouse();
public String toMarry();
public String toMarry2(String name);
}
2.2.2 目标对象
public class User implements UserAction{
@Override
public void toRentHouse() {
System.out.println("租房...");
}
@Override
public String toMarry() {
System.out.println("我结婚了...");
return "XXX结婚了";
}
@Override
public String toMarry2(String name) {
System.out.println(name + "二婚了...");
return "xxx二婚!!!";
}
}
2.2.3 代理对象
- loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。
- interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。
- h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法(传入InvocationHandler接口的子类)。
public class JdkProxy {
private Object target;
public JdkProxy(Object target) {
this.target = target;
}
/**
* 得到代理对象
* newProxyInstance 返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。 (返回代理对 象)
* @return
*/
public Object getProxy(){
//得到类加载器
ClassLoader classLoader=this.getClass().getClassLoader();
//得到目标对象的接口数组
Class[] interfaces=target.getClass().getInterfaces();
//得到InvocationHandler接口 (每当代理对象调用方法时,都会执行InvocationHandler接口中的invoke方法)
InvocationHandler invocationHandler=new InvocationHandler() {
/**
* 当代理对象每调用一次方法,invoke方法都会执行一次
* 1. 执行目标对象的方法
* 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("方法执行前");
//判断参数是否存在
if (args != null && args.length > 0) {
for (Object str: args) {
System.out.println(str);
}
}
Object object = method.invoke(target, args);
System.out.println("方法执行后");
return object;
}
};
//得到代理对象
Object proxy= Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
return proxy;
}
}
2.2.4 JDK动态代理实现目标对象的功能增强
public class JdkProxyTest {
public static void main(String[] args) {
User target=new User();
JdkProxy jdkProxy=new JdkProxy(target);
UserAction userProxy =(UserAction) jdkProxy.getProxy();
//userProxy.toRentHouse();
// 调用有返回值的方法
/*String str=userProxy.toMarry();
System.out.println(str);*/
// 调用有参数的方法
String str=userProxy.toMarry2("张三");
System.out.println(str);
}
}
2.3 CGLIB动态代理
注意:
- 采用继承的思想,定义子类继承父类,父类是目标对象(目标对象不能用final修饰)
- 无论目标对象是否有接口实现,都可以使用CGLIB动态代理
2.3.1 添加依赖
在pom.xml文件中引入cglib的相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
2.3.2 目标对象
public class Person {
public void eat(){
System.out.println("吃饭...");
}
public void drink(){
System.out.println("喝水....");
}
}
2.3.3 代理对象
public class CglibProxy {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
//得到代理对象
public Object getProxy(){
// 得到Enhancer对象(通过对象的create方法,可以得到一个cglib的代理对象)
Enhancer enhancer=new Enhancer();
// 定义代理对象的父类 (目标对象)
enhancer.setSuperclass(target.getClass());
// 得到MethodInterceptor对象,MethodInterceptor接口继承了CallBack接口
MethodInterceptor methodInterceptor=new MethodInterceptor() {
/**
* 当代理对象调用方法时,intercept方法就会被执行
* @param o Cglib动态生成的代理实例 --> personProxy
* @param method 父类中的方法 --> person的eat方法
* @param objects 方法所需要的参数
* @param methodProxy 代理对象中的方法 --> personProxy中的eat方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("当前执行方法:"+method.getName());
System.out.println("饭前洗手...");
Object result = method.invoke(target, objects);
System.out.println("饭后洗碗...");
return result;
}
};
//实现代理过程
enhancer.setCallback(methodInterceptor);
return enhancer.create();
}
}
2.3.4 CGLIB动态代理实现目标对象的功能增强
public class CglibProxyTest {
public static void main(String[] args) {
Person person=new Person();
CglibProxy cglibProxy=new CglibProxy(person);
//得到person的代理对象
Person proxy = (Person) cglibProxy.getProxy();
proxy.drink();
}
}
2.4 JDK代理与CGLIB代理的区别
- JDK动态代理是实现接口,CGLIB动态代理是继承的思想。
- JDK动态代理(目标存在接口时)执行效率高于CIGLIB。
- 如果目标对象有接口实现,选择JDK代理,如果没有接口实现选择CGLIB代理。
三、Spring AOP
3.1 Spring AOP简介
- Spring AOP是面向切面编程,可以实现公共功能的重复使用,它更符合高内聚低耦合的思想,提高了代码的复用性,提高了系统的扩展性,可以在不影响原有功能的基础下添加新的功能。底层是动态代理。
3.2 AOP的基本概念
3.2.1 Joinpoint(连接点)
被拦截到的每个点,spring中指被拦截到的每一个方法,一个连接点代表一个方法的执行。
3.2.2 Pointcut(切入点)
对连接点进行拦截的定义(匹配规则定义规定拦截哪个方法,对哪些方法进行处理)。
3.2.3 Advice(通知)
拦截到每一个连接点即(每一个方法)后所要做的操作。
- Before(前置增强):执行方法前通知。
- AfterReturn(返回增强):方法正常结束返回后的通知。
- After(最终通知):无论方法是否发生异常,均会执行该通知。
- AfterThrow(异常抛出通知):当出现异常时执行该通知。
- Around(环绕通知):最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
**注意:**若这些通知同时存在时,执行的顺序是:环绕通知中的前置通知–>前置通知–>目标方法–>环绕通知的后置通话–>最终通知–>返回增强通知。
3.2.4 Aspect(切面)
切入点与通知的结合,通知定义了方法之后需要做什么(前置增强,返回增强等)。
3.2.5 Target(目标对象)
被代理的目标对象
3.2.6 Weave(织入)
将切面应用到目标对象,并生成代理对象的这个过程即为织入。
3.2.7 Introduction(引入)
在不修改原有应用程序代码的情况下,在程序运行期为类动态的添加方法或者字段的过程称为引入。
四、Spring AOP的实现
4.1 Spring AOP环境搭建
4.1.1 pom.xml文件中引入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
4.1.2 添加spring.xml的配置
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd"
<!-- 开启自动扫描,设置扫描范围 -->
<context:component-scan base-package="com.shsxt"/>
<!-- 开启AOP代理 -->
<aop:aspectj-autoproxy/>
4.2 注解实现
@Component
@Aspect
public class LogCut {
//规定哪些类的哪些方法需要被切面处理
@Pointcut("execution(* com.shsxt.service..*.*(..))")
public void cut(){}
@Before(value = "cut()") //@Before(execution(* com.shsxt.service..*.*(..))")一致
public void before() {
System.out.println("Before -- 方法执行前执行...");
}
@AfterReturning(value = "cut()")
public void afterReturning(){
System.out.println("AfterReturning--方法正常执行后执行...");
}
@After(value="cut()")
public void after(){
System.out.println("After--方法执行后执行...");
}
@AfterThrowing(value="cut()",throwing = "e")
public void afterThrow(Exception e){
System.out.println("AfterThrow--方法执行出现异常时执行..."+e.getMessage());
}
@Around(value = "cut()")
public Object around(ProceedingJoinPoint pjp){
Object object=null;
try{
// 环绕通知 - 前置通知
System.out.println("环绕通知 - 前置通知...");
object = pjp.proceed();
System.out.println("环绕通知 - 返回通知...");
}catch (Throwable throwable){
throwable.printStackTrace();
}
return object;
}
}
4.3 xml实现
4.3.1 定义切面
@Component
public class LogCut {
//规定哪些类的哪些方法需要被切面处理
public void cut(){}
public void before() {
System.out.println("Before -- 方法执行前执行...");
}
public void afterReturning(){
System.out.println("AfterReturning--方法正常执行后执行...");
}
public void after(){
System.out.println("After--方法执行后执行...");
}
public void afterThrow(Exception e){
System.out.println("AfterThrow--方法执行出现异常时执行..."+e.getMessage());
}
public Object around(ProceedingJoinPoint pjp){
Object object=null;
try{
// 环绕通知 - 前置通知
System.out.println("环绕通知 - 前置通知...");
object = pjp.proceed();
System.out.println("环绕通知 - 返回通知...");
}catch (Throwable throwable){
throwable.printStackTrace();
}
return object;
}
}
4.3.2 xml配置文件中实现通知
<!--aop相关配置-->
<aop:config>
<!--aop切面-->
<aop:aspect ref="logCut02">
<!-- 定义aop 切入点 -->
<aop:pointcut id="cut" expression="execution(* com.shsxt.service..*.*(..))"/>
<!-- 配置前置通知 指定前置通知的方法名 并引用切入点定义 -->
<aop:before method="before" pointcut-ref="cut"/>
<!-- 配置返回通知 指定返回通知的方法名 并引用切入点定义 -->
<aop:after-returning method="afterReturn" pointcut-ref="cut"/>
<!-- 配置异常通知 指定异常通知的方法名 并引用切入点定义 -->
<aop:after-throwing method="afterThrow" throwing="e" pointcut-ref="cut"/>
<!-- 配置最终通知 指定最终通知的方法名 并引用切入点定义 -->
<aop:after method="after" pointcut-ref="cut"/>
<!-- 配置环绕通知 指定环绕通知的方法名 并引用切入点定义 -->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>