代理以及aop总结
静态代理
静态代理中有接口、目标实现类、代理类,其中实现类和代理类都需要实现相同接口,因为在静态代理中,目标方法可能有很多方法,而代理类中可能需要代理很多的方法,所以需要使用一个接口对它们间进行约定。
实现:
第一步,创建接口:
public interface TargetInterface {
void method1();
void method2();
int method3(Integer i);
}
第二步,创建被代理的类:
public class Target implements TargetInterface {
@Override
public void method1() {
System.out.println("method1 running ...");
}
@Override
public void method2() {
System.out.println("method2 running ...");
}
@Override
public int method3(Integer i) {
System.out.println("method3 running ...");
return i;
}
}
第三步,创建代理类:
public class TargetProxy implements TargetInterface {
@Override
public void method1() {
System.out.println("执行方法前...");
new Target().method1();
System.out.println("执行方法后...");
}
@Override
public void method2() {
System.out.println("执行方法前...");
new Target().method2();
System.out.println("执行方法后...");
}
@Override
public int method3(Integer i) {
System.out.println("执行方法前...");
int method3 = new Target().method3(i);
System.out.println("执行方法后...");
return method3;
}
}
第四步,测试:
public class TargetUser {
public static void main(String[] args) {
TargetInteface target = new TargetProxy();
target.method1();
System.out.println("-----------------------------");
target.method2();
System.out.println("-----------------------------");
System.out.println(target.method3(3));
}
}
这里可以看到,代理类对被代理类中每一个方法都进行了增强。
缺点:在运行前就已经确定了代理了哪些方法,无法动态地进行代理,这就需要频繁地对目标类创建代理对象,方法和类都会很多,目标类和代理类都需要维护,非常麻烦。
动态代理
JDK动态代理
在静态代理中,每新增一个方法,想要代理此方法时都需要重新维护一下代理类。而动态代理中是在运行时动态创建代理对象,无需编写代理类。
JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象
实现:
第一步,创建接口:
public interface TargetInteface {
void method1();
void method2();
int method3(Integer i);
}
第二步,创建被代理的类:
public class Target implements TargetInteface {
@Override
public void method1() {
System.out.println("method1 running ...");
}
@Override
public void method2() {
System.out.println("method2 running ...");
}
@Override
public int method3(Integer i) {
System.out.println("method3 running ...");
return i;
}
}
第三步,创建代理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TargetProxy {
public static <T> Object getTarget(T t) {
// JDK生成的最终真正的代理类,它继承自Proxy并实现了我们定义的TargetInteface接口
return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
//比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
System.out.println("执行方法前...");
Object invoke = method.invoke(t, args);
System.out.println("执行方法后...");
return invoke;
}
});
}
}
第四步,测试:
public class TargetUser {
public static void main(String[] args) {
TargetInteface target = (TargetInteface) TargetProxy.getTarget(new Target());
target.method1();
System.out.println("-----------------------------");
target.method2();
System.out.println("-----------------------------");
System.out.println(target.method3(3));
}
}
JDK生成的最终真正的代理类,它继承自Proxy并实现了我们定义的TargetInteface接口。
有关jdk动态代理实现和原理,参考:https://jiankunking.blog.csdn.net/article/details/52143504
CGLIB动态代理
cglib代理相对于jdk代理,无需创建统一的接口,只需要一个非抽象类即可实现动态代理。
第一步,创建被代理的类:
public class Target {
public void method1() {
System.out.println("method1 running ...");
}
public void method2() {
System.out.println("method2 running ...");
}
public int method3(Integer i) {
System.out.println("method3 running ...");
return i;
}
}
第二步,创建代理类:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TargetProxy {
static <T> Object getProxy(T t) {
Enhancer en = new Enhancer(); //帮我们生成代理对象
en.setSuperclass(t.getClass());//设置要代理的目标类
en.setCallback(new MethodInterceptor() {//代理要做什么
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("执行方法前。。。");
//调用原有方法
Object invoke = methodProxy.invokeSuper(object, args);
// Object invoke = method.invoke(t, args);// 作用等同与上面。
System.out.println("执行方法后。。。");
return invoke;
}
});
return en.create();
}
}
第三步,测试:
public class TargetUser {
public static void main(String[] args) {
Target target = (Target) TargetProxy.getProxy(new Target());
target.method1();
}
}
总结:
AOP
Spring中的AOP是基于动态代理实现的,即 JDK动态代理和Cglib动态代理。
AOP体系(图来自【SpringBoot-3】切面AOP实现权限校验:实例演示与注解全解_云深不知处-CSDN博客):
AOP各个概念所处的场景:
一、Spring经典AOP实现
第一步,目标接口:
public interface TargetInteface {
void method1();
void method2();
int method3(Integer i);
}
第二步,目标类:
/**
* 目标类
*/
public class Target implements TargetInteface{
/*
* 需要增强的方法,连接点JoinPoint
**/
public void method1() {
System.out.println("method1 running ...");
}
public void method2() {
System.out.println("method2 running ...");
}
public int method3(Integer i) {
System.out.println("method3 running ...");
return i;
}
}
第三步,通知:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class TargetAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice {
/*
* 通知/增强
**/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("前置环绕通知");
Object proceed = methodInvocation.proceed();
System.out.println("后置环绕通知");
return proceed;
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置返回通知");
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知");
}
}
第四步,xml中注册到spring:
三种方式:
one:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="target" class="com.laixm.aopdemo.aop.Target"/>
<bean id="targetAdvice" class="com.laixm.aopdemo.aop.TargetAdvice"/>
<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="target"/> <!--被代理的类-->
<property name="interceptorNames" value="targetAdvice"/> <!--如果用多种增强方式,value的值使用逗号(,)分割-->
<property name="proxyTargetClass" value="true"/>
<property name="interfaces" value="com.laixm.aopdemo.aop.TargetInteface"/> <!--target实现的接口-->
</bean>
</beans>
two:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="target" class="com.laixm.aopdemo.aop.Target"/>
<bean id="targetAdvice" class="com.laixm.aopdemo.aop.TargetAdvice"/>
<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="target"/>
<property name="interceptorNames" value="targetAdvice"/>
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
three:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--目标类-->
<bean id="target" class="com.laixm.aopdemo.aop.Target"/>
<!--通知和切点-->
<bean id="targetAdvice" class="com.laixm.aopdemo.aop.TargetAdvice"/>
<bean id="targetPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*method1"/>
</bean>
<!--制作切面-->
<bean id="pointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="targetAdvice"/>
<property name="pointcut" ref="targetPointcut"/>
</bean>
<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="target"/>
<property name="interceptorNames" value="pointcutAdvisor"/>
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
proxyTargetClass:是否直接使用目标类代替接口进行代理,默认是false,即默认是jdk代理。如果设置成true,如果目标类实现了接口,就会创建一个实现了接口的JDK代理对象。如果目标类没有实现接口,那么就会创建一个CGLIB代理对象。
总结:设置为true的话,需要使用JDK代理的时候,目标类实现接口即可,使用的时候记得转换为接口,因为我们这里获取的是实现了接口的代理对象:
需要使用CGLIB代理的时候,目标类不要实现接口,转换需要用类转:
第五步,测试方法:
public class AopTest {
public static void main(String[] args) {
ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
TargetInteface targetProxy = (TargetInteface) appCtx.getBean("targetProxy"); // jdk动态代理需要转换为接口
targetProxy.method1();
}
}
控制台:
二、Spring纯POJO切面(纯粹通过<aop:fonfig>标签配置)
常用,不可与上面的实现方式同时使用,会产生两个不同的代理对象,对增强执行两次
一、目标类
/*
* 目标类
**/
public class Target {
public void method1() {
System.out.println("method1 running ...");
}
public void method2() {
System.out.println("method2 running ...");
}
/*
* 连接点JoinPoint
**/
public int method3(Integer i) {
System.out.println("method3 running ...");
int i1 = 1 / i;
return i;
}
}
目标类中包含着连接点JoinPoint
二、切面类
/*
* 切面类
**/
public class TargetAspect {
/*
* 前置通知/增强
**/
public void before() {
System.out.println("前置通知");
}
public void after() {
System.out.println("后置通知");
}
public void afterReturning() {
System.out.println("后置返回通知");
}
public void afterThrowing(Exception ex) throws Exception {
System.out.println("异常通知");
System.out.println(ex.getMessage());
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object proceed = null;
if (!"".equals("admin")) {
System.out.println("环绕前置");
proceed = pjp.proceed(pjp.getArgs());
System.out.println("环绕后置");
}
return proceed;
}
}
切面由(Aspect)切入点(Piontcut)和通知(Advice)组成,切入点在xml中配置,也可以使用注解配置
三、配置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: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">
<bean id="target" class="com.laixm.aopdemo.confaop.Target"/>
<bean id="targetAspect" class="com.laixm.aopdemo.confaop.TargetAspect"/>
<!--proxy-target-class="true" 表示使用cglib代理.默认为false,创建有接口的jdk代理-->
<aop:config proxy-target-class="true">
<!--切面:由切入点和通知组成-->
<aop:aspect ref="targetAspect">
<!--切入点 pointcut-->
<aop:pointcut id="pointcut" expression="execution(* com.laixm.aopdemo.confaop.*.*(..))"/>
<!--增强/通知 advice-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:around method="around" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
这种方式实现的aop可读性较高
四、测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
public static void main(String[] args) {
ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-confaop.xml");
Target targetProxy = (Target) appCtx.getBean("target");
System.out.println(targetProxy.method3(0));
}
}
使用切入点表达式匹配切点:todo
三、Spring注解实现AOP
自定义注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation{
}
切面类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/*
* 切面类
**/
@Aspect
@Component
public class AnnotationAspect {
// 定义一个切点:所有被TestAnnotation注解修饰的方法会织入advice
@Pointcut("@annotation(TestAnnotation)")
private void advicePointcut() {}
/*
* 前置通知
**/
@Before("execution(* com.laixm.controller.*.*(..))")
public void before() {
System.out.println("annotation前置通知");
}
@After("advicePointcut()")
public void after() {
System.out.println("annotation后置通知");
}
@AfterReturning(pointcut = "advicePointcut()")
public void afterReturning() {
System.out.println("annotation后置返回通知");
}
@AfterThrowing(pointcut = "advicePointcut()", throwing = "ex")
public void afterThrowing(Exception ex) throws Exception {
System.out.println("annotation异常通知");
System.out.println(ex.getMessage());
}
@Around("advicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object proceed = null;
if (!"".equals("admin")) {
System.out.println("annotation环绕前置");
proceed = pjp.proceed(pjp.getArgs());
System.out.println("annotation环绕后置");
}
return proceed;
}
}
配置
<?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">
<!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象-->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
<!--扫包-->
<context:component-scan base-package="com.laixm.aopdemo.annotationaop"/>
</beans>
测试
@Controller
public class TestController {
@RequestMapping("/test.do")
@ResponseBody
public String testController() {
TestController o = (TestController) AopContext.currentProxy();
o.test();
return "ok";
}
@TestAnnotation
public void test() {
System.out.println("test running");
}
}
**注意:**配置中需要开启:<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>,获取对象的时候需要使用AopContext.currentProxy(); 否则可能导致aop失效。所以建议使用上面第二种方式实现Spring中的aop
之前的代码可查看:https://gitee.com/ilusymon/spring-aop
四、SpringBoot实现注解AOP
https://gitee.com/ilusymon/spring-boot-aop