在spring中,AOP和IOC都是spring非常重要的特性,而在web开发中,定义切面、增强方法也是比较常见的,比如做统一的日志管理相关的、自定义的注解处理、或者在处理用户请求的前后我们需要做一些处理,等等,这时我们都可以使用切面来实现,而在以前,使用切面我们可能需要使用很多接口和类,现在,我们只需要@Aspect这一个注解就可以定义切面。
AOP
AOP(Aspect Oriented Programming,面向切面编程)是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在Spring AOP中业务逻辑仅仅只关注业务本身,将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来,从而在改变这些行为的时候不影响业务逻辑的代码。
相关注解介绍:
注解 | 作用 |
---|---|
@Aspect | 把当前类标识为一个切面 |
@Pointcut | Pointcut是织入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。 |
@Around | 环绕增强,目标方法执行前后分别执行一些代码 |
@AfterReturning | 返回增强,目标方法正常执行完毕时执行 |
@Before | 前置增强,目标方法执行之前执行 |
@AfterThrowing | 异常抛出增强,目标方法发生异常的时候执行 |
@After | 后置增强,不管是抛出异常或者正常退出都会执行 |

1. pom.xml添加aop支持
<!-- 引入aop切面支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 创建自定义注解
@interface是用来自定义JAVA Annotation的语法,
@interface是用来自定义注释类型的
注释类型的定义跟定义一个接口相似,我们需要在 interface这个关键字前面加上一个@符号,即@interface。
注释中的每一个方法定义了这个注释类型的一个元素,注释中方法的声明中一定不能包含参数,也不能抛出异常;方法的返回值被限制为简单类型、String、Class、emnus、注释,和这些类型的数组。方法可以有一个缺省值。
注解仅支持 primitives, string和 enumerations这三种类型。 注解的所有属性都定义为方法,也可以提供默认值。
package com.philos.common.annotation;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface LoginAnno {
String value();
}
上面这个注释里面只定义了一个字符串,它的目标注释对象是类,保留策略是在运行期间。
元注解释义:
java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
@Inherited – 是否允许子类继承该注解
java在jdk5当中支持了这一功能,并且在java.lang.annotation包中提供了四个注解,仅用于编写注解时使用,他们是:
注解 | 作用 |
---|---|
@Documented | 表明是否在java doc中添加Annotation |
@Retention | 定义注释应保留多长时间,即有效周期。有以下几种策略: RetentionPolicy.SOURCE - 在编译期间丢弃。 编译完成后,这些注释没有任何意义,因此它们不会写入字节码。 示例@Override,@ SuppressWarnings RetentionPolicy.CLASS - 在类加载期间丢弃。 在进行字节码级后处理时很有用。 有点令人惊讶的是,这是默认值。 RetentionPolicy.RUNTIME - 不要丢弃。 注释应该可以在运行时进行反射。 这是我们通常用于自定义注释的内容。 |
@Target | 指定可以放置注解的位置。 如果不指定,则可以将注解放在任何位置。若我们只想要其中几个,则需要定义对应的几个下面是这8个属性: ElementType.TYPE(类,接口,枚举) ElementType.FIELD(实例变量) ElementType.METHOD ElementType.PARAMETER ElementType.CONSTRUCTOR ElementType.LOCAL_VARIABLE ElementType.ANNOTATION_TYPE(在另一个注释上) ElementType.PACKAGE(记住package-info.java) |
@Inherited | 控制注解是否对子类产生影响。 |
简单使用注解
下面我们定义一个方法来使用这个注解:
public class UseAnnotation {
@LoginAnno("testStringValue")
public void testMethod(){
//do something here
}
}
我们在这里使用了这个自定义的注解@LoginAnno,并把字符串赋值为:testStringValue,到这里,定义一个注解并使用它,我们就已经全部完成。
3. 创建自定义注解解析
定义切点,并对切点做一些增强操作:前置增强、环绕增强、后置增强等等,切点的定义我们可以在一个空方法体的方法上使用@Pointcut注解
package com.philos.common.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 描述:通过@Aspect注解使该类成为切面类
*/
@Aspect
@Component
public class LoginAnnoImpl {
@Pointcut("@annotation(com.philos.common.annotation.LoginAnno)")
private void cut() {
}
/**
* 功能:前置通知
*/
@Before("cut()")
public void before() {
System.out.println("自定义注解生效了");
}
}
@Pointcut() 注解
@annotation() 类或者方法
@Pointcut()里面定义的是切点表达式,切点表达式有很多,上面例子代码中的是注解表达式,标注来指定注解的目标类或者方法,就比如凡是使用了com.philos.common.annotation.LoginAnno这个注解的类或者方法都是切点。除了@annotation()还有几类比较常见的切点表达式:
execution(方法修饰符 返回类型 方法全限定名 参数) 匹配指定的方法
@Pointcut("execution(* com.tcb.controller.SDProductController.showproductDetail(..))")
- 匹配任意字符,但只能匹配一个元素
.. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
- 必须跟在类名后面,如Student+,表示类本身和继承或扩展指定类的所有类
2.args( 参数类型的类全限定名 )
匹配参数是指定类型的方法,比如@Pointcut(com.xx.Student) 就是匹配所有参数是student类对象的方法,像void add(Student s)这样的方法就会被匹配。
3.@args( 注解类的类全限定名 )
匹配参数的类型的类被指定注解修饰的方法,注意,这个参数的类型还必须是自定义的类,比如@Pointcut(com.xx.anno.ExAnno),你有一个方法void(Student s){},而参数类型Student是你自定义的类,而Student这个类被@ExAnno修饰了,那么,这个方法就会被匹配(这个地方有点坑!)。
4.within( 类全限定名 )
匹配指定的类,比如@Pointcut(com.xx.IndexController)就是IndexController类下的方法都会被匹配,这里的类名支持正则模糊匹配,比如@Pointcut(com.xx.*Controller)就是com.xx包下的所有的Controller都会被匹配。对了,上面和下面的表达式都支持正则模糊匹配
5.target( 类全限定名 )
匹配指定的类以及它的子类,比如@Pointcut(com.xxx.dao.BaseDao)就是匹配BaseDao接口以及所有实现类这个接口的子类。
6.@within( 类全限定名 )
匹配使用了指定的注解的类以及它的子类,比如@Pointcut(com.xxx.anno.Log)就是指,我在BaseController里使用了@Log注解,那么BaseController以及继承BaseController的类都会被匹配。
7.@target( 类全限定名 )
匹配使用了指定注解的类(从这里我们看出来,within、target和@within、@target是相反,郁闷。。。),还是@Pointcut(com.xxx.anno.Log)就是仅匹配使用了@Log注解的类
--
ok,以上是一些较常用的切点表达式,然后继续之前的。
在定义切点之后,我们就可以对切点进行增强操作(注意,我前面@Pointcut注解修饰的方法名是pointcut()哦),比如@Before("pointcut()")前置增强,@Around("pointcut()")环绕增强。。。等等,注意,我这里直接使用的前面定义切点的那个方法名,这种方式属于独立切点命名(划重点!),就是将切点单独定义出来,对于当切点较多时,能够提高一些重用性。这种算是显式地定义切点,除了这种还可以使用匿名切点,即直接在增强操作方法里直接写切点表达式,比如:
@Before("execution(* com.tcb.controller.SDProductController.showproductDetail(..))")
public void beforeBrowse(JoinPoint joinPoint) {}
增强主要有以下几种:
1.@Before
前置增强,在切点方法执行之前执行。这里多说几句,在增强方法中要获取request可以通过下面来获取:
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
在前置增强中,想要获取切点方法的参数可以通过joinPoint.getArgs[]来获取,获取方法名可以通过joinPoint.getSignature().getDeclaringTypeName()来获取。
2.@Around
环绕增强.
3.@AfterReturning
后置增强,切点方法正常执行完返回后执行,如果有异常抛出而退出,则不会执行增强方法
4.@AfterThrowing
后置增强,只有切点方法异常抛出而退出后执行
5.@After
也是后置增强,但不管切点方法是正常退出还是异常退出都会执行
至此自定义注解就编写完毕了,下面来看看调用
4. 使用自定义注解
package com.qfx.common.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.qfx.common.annotation.LoginAnno;
@RestController
@RequestMapping("login")
public class LoginController {
@RequestMapping("reg")
public String reg(String userName) {
return "用户[" + userName +"]注册成功~!";
}
@RequestMapping("login")
@LoginAnno
public String login(String userName) {
return "欢迎您:" + userName;
}
}
网友评论