引子:在实际给别人做业务时,对业务的的要求是一直存在变化的
- 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
- 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
可以使用代理的方式解决上述问题:
代理设计模式原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
两种代理方式:
-
静态代理:
优点:通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
缺点:虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
(1)只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
(2)新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。 -
动态代理:根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。
目录
动态代理分两种:
1.通过实现接口的方式 ->JDK动态代理
案例:对计算器进行动态代理
JiSuanQi:
package 计算器;
public interface JiSuanQi {
public int add(int a,int b);
public int jian(int a,int b);
public int cheng(int a,int b);
public int chu(int a,int b);
}
JiSuanQiImpl:
package 计算器;
public class JiSuanQiImpl implements JiSuanQi {
public int add(int a, int b) {
return a+b;
}
public int jian(int a, int b) {
return a-b;
}
public int cheng(int a, int b) {
return a*b;
}
public int chu(int a, int b) {
return a/b;
}
}
JiSuanQiProxy:
package 计算器;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JiSuanQiProxy {
//被代理的对象
Object target;
public JiSuanQiProxy(Object target){
this.target=target;
}
//通过方法返回一个代理对象
public Object getObject() {
//获取被代理的对象的类加载器
ClassLoader loader = target.getClass().getClassLoader();
//JDK动态代理可以代理多个接口
Class[] interfaces={JiSuanQi.class};
//在匿名内部类中,可以对方法进行标注等处理,比如写日志
InvocationHandler h=new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"被执行之前");
Object o=method.invoke(target,args);
System.out.println(method.getName()+"被执行之后");
return o;
}
};
Object o = Proxy.newProxyInstance(loader, interfaces, h);
return o;
}
}
2.通过继承类的方式 -> CGLIB动态代理
PersonService:
package aop.cglib;
public class PersonService {
public PersonService(){}
public void setPerson(){
}
public void getPerson(){
}
}
CglibProxyIntercepter:
package aop.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyIntercepter implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// Object o是代理对象
System.out.println("方法执行前");
// method.invoke(o,objects);
// Object object = methodProxy.invokeSuper(o, objects);
System.out.println("方法执行后");
return null;
}
}
应用AOP技术:
AOP(面向切面(aspect)编程)是对传统OOP(面向对象编程)的补充。
AOP的好处:
1、每个事务逻辑位于同一位置,不分散,便于维护和升级
2、业务模块更简洁,只包含核心业务代码
在计算器案例中应用AOP技术:
在web.xml中导入所需jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aspectj/aspectjrt -->
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.3</version>
</dependency>
<!-- 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.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.13</version>
</dependency>
JiSuanQi:
package aop;
public interface JiSuanQi {
public int add(int a, int b);
public int mul(int a, int b);
public int cheng(int a, int b);
public int chu(int a, int b);
}
JiSuanQiImpl:
package aop;
import org.springframework.stereotype.Service;
//将类放在IOC容器中
@Service
public class JiSuanQiImpl implements JiSuanQi {
public int add(int a, int b) {
int result=a+b;
return result;
}
public int mul(int a, int b) {
int result=a-b;
return result;
}
public int cheng(int a, int b) {
int result=a*b;
return result;
}
public int chu(int a, int b) {
int result=a/b;
return result;
}
}
AspectjTest:
package aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
//将切面放在IOC容器中
@Component
//声明这是一个切面类
@Aspect
public class AspectjTest {
//添加Before注解,在目标方法执行之前执行此方法
@Before(value = "execution(public int aop.JiSuanQiImpl.add(int,int))")
public void before(){
System.out.println("目标方法执行之前");
}
//添加After注解,在目标方法执行之后执行此方法
@After(value = "execution(public int aop.JiSuanQiImpl.add(int,int))")
public void after(){
System.out.println("目标方法执行之后");
}
}
aop.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: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="aop"></context:component-scan>
<bean id="jiSuanQi" class="aop.JiSuanQiImpl"/>
<!--启用spring 支持 aop注解-->
<aop:aspectj-autoproxy/>
</beans>
AOP更多使用的技巧
优化@Before
对于此类注解
@Before(value = “execution(public int aop.JiSuanQiImpl.add(int,int))”)
当我们的目标方法不止一个时,我们可以使用通配符,方法类型和方法名用*来代替,传参类型用…来代替
@Before(value = “execution(* aop.JiSuanQiImpl.*(…))”)
要是在每个方法上面都要添加这个注解很麻烦,可以使用抽取切入点表达式的方法来简化代码
package aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//将切面放在IOC容器中
@Component
//声明这是一个切面类
@Aspect
public class AspectjTest {
//抽取切入点表达式
@Pointcut(value = "execution(* aop.JiSuanQiImpl.*(..))")
public void cut(){}
//添加Before注解,在目标方法执行之前执行此方法
@Before(value = "cut()")
public void before(JoinPoint point){
System.out.println(point.getSignature().getName()+"目标方法执行之前");
}
//添加After注解,在目标方法执行之后执行此方法
@After(value = "cut()")
public void after(JoinPoint point){
System.out.println(point.getSignature().getName()+"目标方法执行之后");
}
}
返回通知和异常通知的注解
@AfterReturning(value = "cut()",returning = "object")
public void returnResult(JoinPoint joinPoint,Object object){
System.out.println(joinPoint.getSignature().getName()+"返回结果后执行 返回值"+object);
}
@AfterThrowing(value = "cut()",throwing = "e")
public void returnException(Exception e){
System.out.println("异常通知:"+e);
}
环绕通知:
环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是JoinPoint的子接口,允许控制何时执行,是否执行连接点。
在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用joinPoint.proceed();的返回值,否则会出现空指针异常。
设置切面优先级
@Order(1)
可以不在类上加注解,可以将类放在IOC容器中,在IOC容器中对类进行标注操作
<bean id="aspectjTest2" class="aop.AspectjTest2"/>
<aop:config>
<aop:aspect ref="aspectjTest2">
<aop:pointcut id="add" expression="execution(public int aop.JiSuanQiImpl.add(int,int))"/>
<aop:before method="before" pointcut-ref="add"/>
<aop:after method="after" pointcut-ref="add"/>
</aop:aspect>
</aop:config>