概念
面向切面编程:把逻辑代码和处理琐碎事务的代码分离开,以便能够分离复杂度。
切面(AOP)术语
1.连接点(Joinpoint)
2.切点(Pointcut)
3.增强(Advice)
Before advice
After returning advice
After throwing advice
After(finally) advice
Around advice
4.目标对象(Target)
5.引入(Introduction)
6.织入(Weaving)
7.代理(Proxy)
8.切面(Aspect)
优点
更清晰的代码逻辑,业务逻辑只关注自己本身,不用去管琐碎的事情,比如:安全,日志,事务等等。
可以减少代码量
更清晰的代码逻辑,业务逻辑只关注自己本身,不用去管琐碎的事情,比如:安全,日志,事务等等。
可以减少代码量
想了解概念的同学可以去百度或者先看下面的demo 再理解
应用
maven依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
这里我只提供切面需要的依赖
如果不懂maven的同学 可以先看本站的教程
准备工作
// 接口
public interface IUserDao {
void save(Person person);
}
@Component // 加入容器
public class UserDao implements IUserDao{
@Override
public void save(Person person) {
System.out.println("-----核心业务:保存!!!------");
}
}
public class Person {
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
// 接口
public interface IUserDao {
void save(Person person);
}
@Component // 加入容器
public class UserDao implements IUserDao{
@Override
public void save(Person person) {
System.out.println("-----核心业务:保存!!!------");
}
}
public class Person {
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
切面配置
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="net.begincode.aop_anno"></context:component-scan>
<!-- 开启动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="net.begincode.aop_anno"></context:component-scan>
<!-- 开启动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
指定切面
@Component
@Aspect // 指定当前类为切面类
public class Aop {
// 指定切入点表达式: 拦截哪些方法; 即为哪些类生成代理对象
@Pointcut("execution(* net.begincode.aop_anno.*.*(..))")
public void pointCut_(){
}
// 前置通知 : 在执行目标方法之前执行
@Before("pointCut_()")
public void begin(){
System.out.println("开始事务/异常");
}
// 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】
@After("pointCut_()")
public void after(){
System.out.println("提交事务/关闭");
}
// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}
// 异常通知: 当目标方法执行异常时候执行此关注点代码
@AfterThrowing("pointCut_()")
public void afterThrowing(){
System.out.println("afterThrowing()");
}
// 环绕通知:环绕目标方式执行
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
Object[] objs = pjp.getArgs();
Person p = (Person) objs[0];
System.out.println(p.getName());
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}
}
@Component
@Aspect // 指定当前类为切面类
public class Aop {
// 指定切入点表达式: 拦截哪些方法; 即为哪些类生成代理对象
@Pointcut("execution(* net.begincode.aop_anno.*.*(..))")
public void pointCut_(){
}
// 前置通知 : 在执行目标方法之前执行
@Before("pointCut_()")
public void begin(){
System.out.println("开始事务/异常");
}
// 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】
@After("pointCut_()")
public void after(){
System.out.println("提交事务/关闭");
}
// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}
// 异常通知: 当目标方法执行异常时候执行此关注点代码
@AfterThrowing("pointCut_()")
public void afterThrowing(){
System.out.println("afterThrowing()");
}
// 环绕通知:环绕目标方式执行
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
Object[] objs = pjp.getArgs();
Person p = (Person) objs[0];
System.out.println(p.getName());
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}
}
使用junit测试
ApplicationContext ac = new ClassPathXmlApplicationContext("net/begincode/aop_anno/bean.xml");
@Test
public void testApp() {
IUserDao userDao = (IUserDao) ac.getBean("userDao");
System.out.println(userDao.getClass());//$Proxy001
Person person = new Person();
person.setName("abc");
person.setSex("man");
userDao.save(person);
}
ApplicationContext ac = new ClassPathXmlApplicationContext("net/begincode/aop_anno/bean.xml");
@Test
public void testApp() {
IUserDao userDao = (IUserDao) ac.getBean("userDao");
System.out.println(userDao.getClass());//$Proxy001
Person person = new Person();
person.setName("abc");
person.setSex("man");
userDao.save(person);
}
输出:
class com.sun.proxy.$Proxy15
abc
环绕前....
开始事务/异常
-----核心业务:保存!!!------
环绕后....
提交事务/关闭
afterReturning()
如果目标对象有实现接口,spring会自动选择"JDK代理"
如果目标对象没有实现接口,spring就会使用"cglib"代理
AOP的应用很广泛 功能很强大 可以做:安全,日志,事务等等。
这里介绍的只是最基本使用方法
参考文献:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
具体应用
在begincode 问答系统中 我们利用切面去拦截controller,使指定的入参被拦截
@Aspect
@Component
public class RequestAspect {
@Pointcut("execution(* net.begincode.controller.*.*(..))")
public void pointCut_() {
}
@Around("pointCut_()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object[] objects = proceedingJoinPoint.getArgs();
for (int i = 0; i < objects.length; i++) {
if (objects[i] instanceof ProblemLabelParam) {
ProblemLabelParam problemLabelParam = (ProblemLabelParam) objects[i];
problemLabelParam.check();
}
}
return proceedingJoinPoint.proceed();
}
}
拦截所有从controller进去的参数 并且验证有没有 ProblemLabelParam 类型的参数
如果有的话,则调用其中的check方法验证传入的参数是否有异常
部分代码如下:
public class ProblemLabelParam extends Param{
....
@Override
public void check() {
checkNotEmpty(problem.getContent(),ProblemResponseEnum.PROBLEM_ADD_ERROR);
checkNotEmpty(problem.getTitle(),ProblemResponseEnum.PROBLEM_ADD_ERROR);
}
....
}
我们规定入参对象必须全部继承Param类 不能使用原有的model直接进入方法
并且实现抽象的check方法
public abstract class Param {
public abstract void check();
public void checkNotNull(Object value, ResponseEnum status) {
checkArgs(value != null, status);
}
public void checkNotEmpty(String value, ResponseEnum status) {
checkArgs(StringUtils.isNotBlank(value), status);
}
public void checkArgs(boolean success, ResponseEnum status) {
if (!success) {
throw new BizException(status);
}
}
}