用了那么久Spring,该是做个总结的时候了
一、Spring AOP介绍
AOP思想是基于代理模式, 因为Java中一般采用JDK动态代理模式,由于Spring同事支持CGLIB、AspectJ、JDK动态代理,但是JDK动态代理只能 代理接口不能代理类,所以Spring会这样子进行切换:
(1)如果目标对象的实现类实现了接口,则直接采用JDK动态代理生成AOP代理类;
(2)如果目标对象类没有实现接口,SpringAOP则会采用CGLIB来生成AOP代理类
1.1、什么是AOP
AOP,即面向切面编程。允许通过分离应用的业务逻辑与系统级服务(如审计、日志、权限校验、事物管理)进行内聚性的开发——原理就是使用代理模式。。应用对象只实现它们应该做的事情——完成业务逻辑,仅此而已。
1.2、AOP基本概念
(1)通知
通知有五种类型:
-
Before
在方法被调用之前调用 -
After
在方法完成后调用通知,无论方法是否执行成功 -
After-returning
在方法成功执行之后调用通知 -
After-throwing
在方法抛出异常后调用通知 -
Around
通知了好、包含了被通知的方法,在被通知的方法调用之前后调用之后执行自定义的行为
(2)切点(Pointcut)
切点在AOP中对应系统中的方法,这个方法是定义在切面中的方法,一般和通知一起使用,一起组成切面
(3)连接点(Join point)
就是方法调用、方法执行、字段设置/获取、异常处理执行、类初始化、for循环某个点等。总的来说就是准备在系统中执行切点和切入通知的地方(一般是一个方法、一个字段)
(4)切面(Aspect)
切面是切点和通知的集合,一般单独作为一个类。通知和切点共同定义关于切面的全部内容,它是什么时候在何时何处完成功能。
(5)引入(Introduction)
允许向现有类添加新的方法或者属性
(6)织入(Weaving)
组装切面来创建一个被通知的对象。这可以在编译时完成,也可以在运行时完成 (Spring是在运行时完成)
1.3、Spring AOP基于AspectJ注解如何实现AOP
(1)什么是AspectJ(静态织入)
是一个AOP框架,它能对Java代码进行AOP编译,让Java代码具有AspectJ的AOP功能,Spring只是使用了AspectJ5一样的注解,但是没使用它的编译器,底层依然是动态代理技术的实现;同理Spring AOP虽然使用了那一套注解,其实实现AOP的底层还是动态代理(JDK或者CGLIB)来动态织入
1.4、AOP通知类型
(1)@Before 前置通知(Before advice):在某连接点之前执行的通知
(2)@After 后通知(After advice):当某连接点退出时执行的通知(无论正常返回还是抛出异常)
(3)@AfterReturning 返回后通知(After return advice):在某连接点正常完成后的通知(不包括抛出异常)
(4)@Around 环绕通知(Around advice):包围一个连接点的通知,可以在方法调用前后完成自定义行为
(5)@AfterThrowing 抛出异常后通知(After throwing advice) :在方法抛出异常时退出执行的通知
二、AOP代码部分
2.1、代码理解AOP基本概念
(1)定义主题接口,接口方法为连接点
public interface Subject {
//登陆
public void login();
//下载
public void download();
}
(2)定义实现类,这是代理模式中真正的被代理人
public class SubjectImpl implements Subject {
public void login() {
System.err.println("借书中...");
}
public void download() {
System.err.println("下载中...");
}
}
(3)定义切面(切面中有 切点和通知)
public class PermissionVerification {
/**
* 权限校验
* @param args 登陆参数
*/
public void canLogin() {
//做一些登陆校验
System.err.println("我正在校验啦!!!!");
}
/**
* 校验之后做一些处理(无论是否成功都做处理)
* @param args 权限校验参数
*/
public void saveMessage() {
//做一些后置处理
System.err.println("我正在处理啦!!!!");
}
}
(4)SpringAOP.xml
文件(老式方法,基于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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="SubjectImpl1" class="wokao666.club.aop.spring02.SubjectImpl" />
<bean id="SubjectImpl2" class="wokao666.club.aop.spring02.SubjectImpl" />
<bean id="PermissionVerification" class="wokao666.club.aop.spring01.PermissionVerification" />
<aop:config>
<!-- 这是定义一个切面,切面是切点和通知的集合-->
<aop:aspect id="do" ref="PermissionVerification">
<!-- 定义切点 ,后面是expression语言,表示包括该接口中定义的所有方法都会被执行-->
<aop:pointcut id="point" expression="execution(* wokao666.club.aop.spring01.Subject.*(..))" />
<!-- 定义通知 -->
<aop:before method="canLogin" pointcut-ref="point" />
<aop:after method="saveMessage" pointcut-ref="point" />
</aop:aspect>
</aop:config>
</beans>
(5)测试类和结果
public class Test {
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("SpringAOP.xml");
Subject subject1 = (Subject)ctx.getBean("SubjectImpl1");
Subject subject2 = (Subject)ctx.getBean("SubjectImpl2");
subject1.login();
subject1.download();
System.err.println("==================");
subject1.login();
subject1.download();
}
}
2.2、动态AOP的简单使用示例:利用AOP实现自定义Spring注解
自定义Spring注解原理(自研框架里很多自定义注解):通过AOP的面向切面功能,在指定代码位置织入我们想要的代码,通过参数的形式传递给切面处理程序,然后执行解析。解析完后可以做想要的操作了。
(自定义注解可以使用AOP实现,也可以使用拦截器实现,推荐AOP)
示例:通过AOP定义注解记录日志信息
(1)定义注解类
package net.itaem.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 说明:
* 定义日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FileLog {
String value() default "日志记录开始";
}
(2)定义注解的处理程序
package net.itaem.aspect;
import net.itaem.annotation.FileLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 说明:
* 日志的切面处理程序
*/
@Aspect
@Component
public class LogAspect {
@AfterReturning("within(net.itaem..*) && @annotation(fl)")
public void addSuccessLog(JoinPoint jp, FileLog fl){
Object[] parames = jp.getArgs();
//获取目标方法体参数
String className = jp.getTarget().getClass().toString();
//获取目标类名
String signature = jp.getSignature().toString();
//获取目标方法签名
String methodName = signature.substring(signature.lastIndexOf(".")+1, signature.indexOf("("));
//获取注解值
String desc=fl.value();
//把调用的信息写到日常记录信息里面去...
}
//标注该方法体为异常通知,当目标方法出现异常时,执行该方法体
@AfterThrowing(pointcut="within(net.itaem..*) && @annotation(fl)", throwing="e")
public void addExceptionLog(JoinPoint jp, FileLog fl, Exception e){
//把错误信息写到错误日志文件里面去...
}
}
(3)编写测试用例(老方法,XML形式)
配置spring启动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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd ">
<aop:aspectj-autoproxy/>
<bean name="purchaseService" class="net.itaem.test.PurchaseService" />
<bean class="net.itaem.aspect.LogAspect"/>
</beans>
(4)编写service类
public class PurchaseService {
public void purchaseProduct(String productName,int price,String type) {
System.out.println("购买商品。。。");
}
}
(5)编写测试代码
package net.itaem.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void test() {
ApplicationContext context=new ClassPathXmlApplicationContext("net/itaem/source/test.xml");
PurchaseService service=(PurchaseService) context.getBean("purchaseService");
service.purchaseProduct("电风扇", 98, "日用品");
}
}
(6)结果展示
下一篇:Spring IoC控制反转
参考资料 :https://blog.csdn.net/fighterandknight/article/details/51209822