Spring 之 AOP
- 传统的OOP编程,多个类公用同一方法,修改时就要修改所有的文件
- AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时再将这些提取出来的代码应用到需要执行的地方
- AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面
-
AOP采用了横向抽取机制,而不是纵向继承,其实就是可以理解为一个
动态代理类
,原类的增强 - aop的用处,权限校验、日志、性能监控
- 动态代理 有 JDK动态代理 (只能对实现了接口的类产生代理对象 )和 Cglib动态代理 (其实就是子类实现) ,spring底层判断有没有实现接口,实现了用前者,否则用后者
- spring的AOP是(AspectJ的xml配置,AspectJ是AOP的框架,以前Spring有自己的实现比较繁琐,所以使用了AspectJ)
基于JDK的实现
// 1 定义接口
public interface Calc {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
// 2 实现接口
public class CalcImpl implements Calc {
@Override
public int add(int i, int j) {
System.out.println("this is add !!!");
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
// 3 实现jdk代理类
public class JDKProxy implements InvocationHandler {
private Calc calc ;
public JDKProxy(Calc calc) {
this.calc = calc;
}
public Calc createProxy(){
Calc calc1 = (Calc) Proxy.newProxyInstance(
calc.getClass().getClassLoader(), // 类加载器
calc.getClass().getInterfaces(),
this
);
return calc1;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断方法名是不是add
if("add".equals(method.getName())){
System.out.println(new Date());
}
return method.invoke(calc, args);
}
}
// 4. 调用测试
public static void main(String[] args) {
Calc calcImpl = new CalcImpl();
Calc proxy = new JDKProxy(calcImpl).createProxy();
proxy.add(1, 2);
}
基于CgLib的动态代理
// 1 新建类 因为CgLib可以代理普通类
public class CalcDao {
public void save(){
System.out.println("save");
}
}
// 2 实现CgLibProxy
public class CgLibProxy implements MethodInterceptor {
private CalcDao calcDao;
public CgLibProxy(CalcDao calcDao) {
this.calcDao = calcDao;
}
public CalcDao createProxy(){
// 创建cglib核心类对象
Enhancer enhancer = new Enhancer();
// 设置父类,因为cglib的实现,是基于继承实现的
enhancer.setSuperclass(calcDao.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建代理对象
CalcDao proxy = (CalcDao) enhancer.create();
return proxy;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
if("save".equals(method.getName())){
System.out.println("enhancer");
}
return methodProxy.invokeSuper(proxy, args);
}
}
// 3 调用测试
public static void main(String[] args) {
CalcDao calcDao = new CalcDao();
CalcDao proxy1 = new CgLibProxy(calcDao).createProxy();
proxy1.save();
}
正式使用Spring的AOP
1 导包
spring aop 和 spring aspects (spring 和 AspectJ整合的包 )
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
--------------------- --------------------- --------------------- ---------------------
2 创建spring的核心配置文件,导入Aop的约束:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
</beans>
--------------------- --------------------- --------------------- ---------------------
3. 新建目标对象
public interface ProductDao {
public void save();
public void update();
public void find();
public void delete();
}
public class ProductDaoImpl implements ProductDao {
@Override
public void save() {
System.out.println("save");
}
@Override
public void update() {
System.out.println("update");
}
@Override
public void find() {
System.out.println("find");
}
@Override
public void delete() {
System.out.println("delete");
}
}
--------------------- --------------------- --------------------- ---------------------
4. 新建切面类
/**
* 切面类
*/
public class MyAspectXML {
public void check(){
System.out.println("'权限校验'");
}
}
--------------------- --------------------- --------------------- ---------------------
5. 配置到xml中
<!--目标对象,被增强的对象 -->
<bean id="productDao" class="com.starbugs.wehcat.aop.product.ProductDaoImpl" />
<!--切面类交给spring-->
<bean id="myAspect" class="com.starbugs.wehcat.aop.product.MyAspectXML" />
<!--aop配置对目标类产生代理-->
<aop:config>
<!--expression配置哪些类的哪些方法需要增强-->
<aop:pointcut id="pointcut1" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.save(..))" />
<!--配置切面-->
<aop:aspect ref="myAspect" >
<aop:before method="check" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
通知类型
1 前置通知 目标方法执行前
2 后置通知 可以阻止目标方法的执行
3 环绕通知 前和后
4 异常抛出通知
5 最终通知 无论代码有异常,都执行
1 xml配置
<aop:config>
<!--expression配置哪些类的哪些方法需要增强-->
<aop:pointcut id="pointcut1" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.save(..))" />
<aop:pointcut id="pointcut2" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.update(..))" />
<aop:pointcut id="pointcut3" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.delete(..))" />
<aop:pointcut id="pointcut4" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.find(..))" />
<!--配置切面-->
<aop:aspect ref="myAspect" >
<!-- 前置信息 可以获取切入点的信息 -->
<aop:before method="check" pointcut-ref="pointcut1"/>
<!--后置通知 还可以获得返回值 -->
<aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="res"/>
<!--环绕通知-->
<aop:around method="monitor" pointcut-ref="pointcut3" />
<aop:after-throwing method="exe" pointcut-ref="pointcut4" throwing="ex"/>
<aop:after method="after" pointcut-ref="pointcut4" />
</aop:aspect>
</aop:config>
2 增强类
public class MyAspectXML {
// 前置通知 joinPoint 切入点信息
public void check(JoinPoint joinPoint){
System.out.println("'权限校验'" + joinPoint);
}
// 后置通知
public void writeLog(JoinPoint joinPoint, Object res){
System.out.println("writeLog !!!" + joinPoint);
System.out.println(res);
}
// 环绕通知
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知 之前");
Object obj = joinPoint.proceed();
System.out.println("环绕通知 之后");
return obj;
}
// 异常抛出通知
public void exe(Throwable ex){
System.out.println("异常抛出通知" + ex.getMessage());
}
public void after(){
System.out.println("最终通知");
}
}
spring AOP 表达式
这里有一点需要注意是表达式,可以用逻辑运算符
&& || !
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws- pattern?
modifiers-pattern:表示定义的目标方法的访问修饰符,如public、private等。
ret-type-pattern:表示定义的目标方法的返回值类型,如void、String等。
declaring-type-pattern:表示定义的目标方法的类路径,com.ssm.jdk.UserDaoImpl。 name-pattern:表示具体需要被代理的目标方法,如add()方法。
param-pattern:表示需要被代理的目标方法包含的参数,本章示例中目标方法参数都为空。
throws- pattern:表示需要被代理的目标方法抛出的异常类型
// demo1
execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))
述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须String类型的方法
// demo2
execution(* com.spring.service.BusinessObject.*())
表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法
// demo3
execution(* com.spring.service.Business*.*())
表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法
// demo4 ..通配符,该通配符表示0个或多个项 主要用于任意参数或任意路径
execution(* com.spring.service..*.businessService())
匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数
** this和target**
- this和target表达式中都只能指定类或者接口
- 类A和B,A调B某方法,如切点表达式为this(B),那么A的实例将会被匹配,也即其会被使用当前切点表达式的Advice环绕;如果这里切点表达式为target(B),那么B的实例也即被匹配,其将会被使用当前切点表达式的Advice环绕
-
this匹配
代理对象
为指定类型的类。 -
target匹配
业务对象为
指定类型的类。
目标对象与代理对象
- 如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口;
- 如果目标对象是一个普通类,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象
this和target使用3种情况
- this(interfaceA)和 target(interfaceA),效果一样
- this(普通类 )和 target(普通类),效果一样
- this(接口类) 和 target(接口类),this 代理类不是接口类的类型,所以不一样
within
within表达式的粒度为类,其参数为全路径的类名(可使用通配符)
within的语义表示匹配指定类型的类实例,这里的@within表示匹配带有指定注解的类
// 表示 com.spring.service.BusinessObject下边所有的方法
within(com.spring.service.BusinessObject)
// 修饰一个类
@Around("@within(com.demo.annotation.Aspect1)")
annotation
匹配使用@annotation指定注解标注的方法将会被环绕
// 修饰一个方法
@Around("@annotation(com.demo.annotation.Aspect2)")
args
args表达式的作用是匹配指定参数类型和指定参数数量的方法
args(java.lang.String,..,java.lang.Integer)
// 参数是某个注解的类被匹配
@Around("@args(com.demo.Aspect3)")
spring boot中使用
首先,导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后建一个切面类即可
package com.starbugs.wehcat.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* 日志切面
* @author starbooks
*
* 1 @Aspect 作用是把当前类标识为一个切面供容器读取
* 2 @Pointcut 建立一个切入点
*/
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.starbugs.wehcat.controller.FirstController.*(..)) || execution(* com.starbugs.wehcat.controller.StudentController.*(..))")
public void webLog(){
}
@Before("webLog()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("IP : " + request.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
System.out.println("方法的返回值 : " + ret);
}
//后置异常通知
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp){
System.out.println("方法异常时执行.....");
}
//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("webLog()")
public void after(JoinPoint jp){
System.out.println("方法最后执行.....");
}
//环绕通知,环绕增强,相当于MethodInterceptor
// @Around参数必须为ProceedingJoinPoin
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) {
System.out.println("方法环绕start.....");
try {
Object o = pjp.proceed();
System.out.println("方法环绕proceed,结果是 :" + o);
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
自定义注解
1. 注解的常用地方
1.利用注解实现AOP拦截以及操作日志记录
2.利用注解实现对前端参数的数据有效性校验。
3.利用注解结合反射获取java bean属性字段的额外信息
2.注解的定义
@interface
表示注解的定义
@target
表示该注解的作用域 有 CONSTRUCTOR, FIELD, METHOD, TYPE
@retention
表示注解类型保留时间的长短,它接收RetentionPolicy参数,可能的值有SOURCE, CLASS, 以及RUNTIME,我们常用runtime,表示在编译以及java vm都会保存,所以可以用来反射阶段获取字段的额外属性值
@Documented
表示该注解可以被javadoc等工具文档
@constraint
表示注解的约束关系,属性值validateBy这个属性是设定约束关系的实现类
package com.example.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
String desc() default "default";
}
参考资料
https://www.cnblogs.com/zhangxufeng/p/9160869.html
https://www.cnblogs.com/chenziyu/p/9547343.html