AOP面向切面编程

代理模式

静态代理

角色分析:
抽象角色:一般使用接口或抽象类来实现。一是通过继承被代理类的方式实现其子类,重写父类方法;二是与被代理类实现共同的一个接口。
真实角色:被代理的角色。
代理角色:代理真实角色,执行附属操作。
客户:使用代理角色来进行一些操作。
代码步骤 :

1、接口

1
2
3
public interface Rent {
public void rent();
}

2、真实角色

1
2
3
4
5
6
public class Host implements Rent {
@Override
public void rent() {
System.out.println("Host提供租房服务~");
}
}

3、代理角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Proxy {

private Host host;

public Proxy() {
}

public Proxy(Host host) {
this.host = host;
}

// 代理Host对象提供服务
public void rent(){
host.rent();
}
}

4、客户端访问代理角色

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
}

好处:
1、可以使真实角色的操作更加纯粹,不用去关注其他公共的业务
2、公共业务交给代理角色,实现业务分工
3、公共业务发生拓展时,方便集中管理
缺点:
一个真实角色就会产生一个代理角色,造成代码量翻倍

动态代理

代理类是动态生成的,不是我们直接写好的。交给程序去自动生成代理类(在程序运行期间由JVM根据反射等机制动态的生成源码 )。
动态代理分为两大类:基于接口的JDK动态代理;基于类cglib动态代理。
1、接口

1
2
3
4
5
6
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}

2、真实角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加");
}

@Override
public void delete() {
System.out.println("删除");
}

@Override
public void update() {
System.out.println("修改");
}

@Override
public void query() {
System.out.println("查询");
}
}

3、实现InvocationHandler接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyInvocationHandler implements InvocationHandler {

// 目标对象
private Object target;

public void setTarget(Object target) {
this.target = target;
}

// 通过代理对象执行方法时,会调用invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行了MyInvocationHandler中的invoke方法");
// 执行目标类方法
return method.invoke(target,args);
}
}

4、创建并使用代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
// 创建目标对象
UserServiceImpl target = new UserServiceImpl();
// 创建MyInvocationHandler对象
MYInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.setTarget(target);
// 使用Proxy创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), myInvocationHandler);
// 通过代理执行方法,会调用myIvocationHandler中的invoke方法
proxy.add();
}
}

动态代理作用:
1、在目标类源代码不改变的情况下,增加功能。
2、减少代码重复
3、专注业务逻辑代码
4、解耦合

AOP定义

面向切面编程,基于动态代理,可使用jdk,cglib两种代理方式。
Spring会根据具体的Bean是否具有实现接口去选择动态代理方式,如果有接口,使用的是Jdk的动态代理方式,如果没有接口,使用的是cglib的动态代理方式。
AOP就是动态代理的规范化,供开发人员以统一的方式使用动态代理。
比如你写了个方法用来做一些事情,但这个事情要求登录用户才能做,你就可以在这个方法执行前验证一下,执行后记录下操作日志,把前后的这些与业务逻辑无关的代码抽取出来放一个类里,这个类就是切面(Aspect),这个被环绕的方法就是切点(Pointcut),你所做的执行前执行后的这些方法统一叫做增强处理(Advice)。

面向切面编程实现:
1、需要在分析项目功能时,找出切面
2、合理的安排切面的执行时间(目标方法前,还是目标方法后)—->通知注解
3、合理的安排切面执行的位置(在哪个类,哪个方法增加增强方法)—->切入点表达式

AOP实现

aspectj:开源的aop实现框架
加入aspectj依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

1、真实对象的接口:

1
2
3
public interface SomeService {
public void doSome();
}

2、实现真实对象

1
2
3
4
5
6
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("doSome方法执行");
}
}

3、切面对象,用于给业务增加功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @Aspect 表示当前类是切面类。
* 切面类:用于给业务方法增加功能
*/
@Aspect
public class MyAspect {
/**
* @Before 前置通知
* 定义方法,实现切面功能
* 必须为 public void
* 切入点表达式:execution(访问修饰符 返回值 包名.类名.方法名(方法参数) 异常类型)
*/

/**@Pointcut("execution(* com.zyz.service..*.pageList*(..))")
*public void pagePointcut(){}


*@Before("pagePointcut()")
*/
@Before(value = "execution(public void com.zyz.SomeServiceImpl.doSome())")
public void mybefore(JoinPoint joinPoint){// JoinPoint 获取执行方法的信息 必须是第一个参数
// 获取方法名
System.out.println(joinPoint.getSignature().getName());
System.out.println("前置通知:"+new Date());
}
}

常用Pointcut表达式

拦截指定注解下的所有方法

1
@Pointcut("@annotation(com.zyz.advice.aspect.Log)")

拦截所有公共方法

1
@Pointcut("execution(public * *(..))")

拦截以set开头的所有方法

1
@Pointcut("execution(* set*(..))")

拦截类或接口中的所有方法

1
@Pointcut("execution(* com.zyz.service.xxxService.*(..))")

拦截包中定义的方法,不包含子包中的方法

1
@Pointcut("execution(* com.zyz.service.*.*(..))")

拦截包或者子包中定义的方法

1
@Pointcut("execution(* com.zyz.service..*.*(..))")

4、添加配置文件,由spring创建需要的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 将对象交给spring容器,由spring容器统一创建,管理对象-->
<!-- 声明目标对象-->
<bean id="someService" class="com.zyz.SomeServiceImpl"/>
<!-- 声明切面对象-->
<bean id="myAspection" class="com.zyz.MyAspect"/>
<!-- 声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象
将spring容器中所有目标对象,一次性生成代理对象-->
<aop:aspectj-autoproxy />
</beans>

5、测试

1
2
3
4
5
6
7
@Test
public void test1() {
String config = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) applicationContext.getBean("someService");
proxy.doSome();
}

结果:

后置通知:

1
2
3
4
5
6
7
8
/**
* returning 目标方法返回值
* @param res
*/
@AfterReturning(value = "execution(public Integer com.zyz.SomeService.doOther(Integer))",returning = "res")
public void myAfterReturn(Object res){
System.out.println("后置通知 获取的返回值为"+res);
}

环绕通知:

1
2
3
4
5
6
7
8
9
10
11
/**
* @param proceedingJoinPoint 固定参数
*/
@Around(value = "execution(public Integer com.zyz.SomeService.doAround(Integer))")
public void myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知:目标方法前");
Object res = null;
res = proceedingJoinPoint.proceed();// method.invoke()
System.out.println("目标方法返回值:"+res);
System.out.println("环绕通知:目标方法后");
}