标签:point 支持 his orm 如何 列表 prope icm 实现
一、概述
按照软件重构思想的理念,如果多个类中出现相同的代码,应该考虑定义共同的抽象类。但并非所有情况下上述方法都是可行的,有时我们无法通过父类的方式消除重复性的横切代码,因为这些横切逻辑依附在业务类方法的流程中,不能转移到其他地方去。
面向切面编程(AOP)通过横向抽取机制为这类无法通过纵向继承进行抽象的重复性代码提供了解决方案。此前需要了解一些AOP概念
AOP的工作重心在于如何将增强应用于目标对象的连接点上,主要内容包括如何通过切点和增强定位连接点和如何在增强中编写切面的代码。
二、基础知识
具有横切逻辑的代码实例。
//Recorder.java package aop; public class Recorder { private long begin; private String method; public Recorder(String method) { begin = System.currentTimeMillis(); this.method = method; } public void print() { long end = System.currentTimeMillis(); System.out.println(String.format("%s: %d ms", method, end - begin)); } } //Monitor.java package aop; public class Monitor { private static ThreadLocal<Recorder> threadLocal = new ThreadLocal<>(); public static void begin(String method) { System.out.println("begin monitor"); Recorder recorder = new Recorder(method); threadLocal.set(recorder); } public static void end() { System.out.println("end monitor"); Recorder recorder = threadLocal.get(); recorder.print(); } } //Service.java package aop; public interface Service { void testMethod1(); void testMethod2(); } package aop; public class Service implements Service { @Override public void testMethod1() { Monitor.begin("aop.Service.testMethod1"); System.out.println("Hello!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Monitor.end(); } @Override public void testMethod2() { Monitor.begin("aop.Service.testMethod2"); System.out.println("Bonjour!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Monitor.end(); } } //Solution.java import aop.Service; class Solution { public static void main(String[] args) { Service service = new Service(); service.testMethod1(); service.testMethod2(); } }
JDK提供了动态代理技术,允许开发者在运行期创建接口的代理实例,该技术是实现AOP的基础。动态代理主要涉及到的类为Proxy和InvocationHandler,使用InvocationHandler接口定义横切逻辑后通过反射条用目标类的代码,动态将横切逻辑和业务逻辑编织在一起;Proxy利用InvocationHandler创建特定接口的实例,生成目标类代理对象。优化后代码如下:
//ServiceImpl.java package aop; public class ServiceImpl implements Service { @Override public void testMethod1() { System.out.println("Hello!"); } @Override public void testMethod2() { System.out.println("Bonjour!"); } } //Solution.java import aop.Service; import aop.ServiceHandler; import aop.ServiceImpl; import java.lang.reflect.Proxy; class Solution { public static void main(String[] args) { Service service = new ServiceImpl(); ServiceHandler handler = new ServiceHandler(service); Service proxy = (Service) Proxy.newProxyInstance( service.getClass().getClassLoader(), service.getClass().getInterfaces(), handler); proxy.testMethod1(); proxy.testMethod2(); } }
使用JDK创建代理有只能为接口创建实例代理的限制。CGLib弥补了该缺陷,通过采用底层字节码技术为特定类创建其子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,之后顺势织入横切逻辑,从而实现动态创建类的代理实例。
//CGLibProxy.java package aop; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CGLibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class cls) { enhancer.setSuperclass(cls); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Monitor.begin(String.format("%s.%s", o.getClass().getName(), method.getName())); Object obj = methodProxy.invokeSuper(o, objects); Thread.sleep(1000); Monitor.end(); return obj; } } //Solution.java import aop.CGLibProxy; import aop.ServiceImpl; class Solution { public static void main(String[] args) { CGLibProxy proxy = new CGLibProxy(); ServiceImpl service = (ServiceImpl) proxy.getProxy(ServiceImpl.class); service.testMethod1(); service.testMethod2(); } }
三、增强类
按照增强在目标类方法的连接点位置,Spring的增强可分为五种类型
ProxyFactory是上述的JDK代理和CGLib代理的实现,负责将Advice应用到目标类中并获取代理类。
//Person.java package aop; public interface Person { void read(String s) throws Throwable; } //PersonImpl.java package aop; public class PersonImpl implements Person { @Override public void read(String s) throws Throwable { if (!s.isEmpty()) { System.out.println("I‘m reading " + s); } else { throw new IllegalArgumentException("I found nothing to read"); } } } //Singer.java package aop; public interface Singer { void sing(String s) throws Throwable; } //PersonBeforeAdvice.java package aop; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class PersonBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) { System.out.println("person before advice called"); } } //PersonAfterAdvice.java package aop; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class PersonAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) { System.out.println("person after advice called"); } } //PersonAroundAdvice.java package aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class PersonAroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("person around advice before proceed"); Object obj = methodInvocation.proceed(); System.out.println("person around advice after proceed"); return obj; } } //PersonThrowsAdvice.java package aop; import org.springframework.aop.ThrowsAdvice; import java.lang.reflect.Method; public class PersonThrowsAdvice implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object obj, RuntimeException e) { System.out.println("person throws advice called"); } } //PersonIntroduction.java package aop; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; public class PersonIntroduction extends DelegatingIntroductionInterceptor implements Singer { @Override public void sing(String s) { if (!s.isEmpty()) { System.out.println("I‘m singing " + s); } else { throw new IllegalArgumentException("I found nothing to sing"); } } @Override public Object invoke(MethodInvocation mi) throws Throwable { return super.invoke(mi); } } //Solution.java import aop.*; import org.springframework.aop.framework.ProxyFactory; class Solution { public static void main(String[] args) { Person person = new PersonImpl(); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(person); proxyFactory.setInterfaces(Person.class, Singer.class); proxyFactory.setOptimize(true); proxyFactory.addAdvice(new PersonBeforeAdvice()); proxyFactory.addAdvice(new PersonAfterAdvice()); proxyFactory.addAdvice(new PersonAroundAdvice()); proxyFactory.addAdvice(new PersonThrowsAdvice()); proxyFactory.addAdvice(new PersonIntroduction()); Object proxy = proxyFactory.getProxy(); try { ((Person) proxy).read("Hello!"); ((Singer) proxy).sing(""); } catch (Throwable throwable) { System.out.println(throwable.getMessage()); } } }
ProxyFactoryBean负责为其他Bean创建代理实例,内部使用ProxyFactory来实现。其可配置属性如下
通过ProxyFactoryBean可以实现XML配置代理。
//spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="beforeAdvice" class="aop.PersonBeforeAdvice"/> <bean id="afterAdvice" class="aop.PersonAfterAdvice"/> <bean id="aroundAdvice" class="aop.PersonAroundAdvice"/> <bean id="throwsAdvice" class="aop.PersonThrowsAdvice"/> <bean id="introduction" class="aop.PersonIntroduction"/> <bean id="person" class="aop.PersonImpl"/> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean" p:optimize="true" p:target-ref="person"> <property name="interfaces"> <list> <value>aop.Person</value> <value>aop.Singer</value> </list> </property> <property name="interceptorNames"> <list> <idref bean="beforeAdvice"/> <idref bean="afterAdvice"/> <idref bean="aroundAdvice"/> <idref bean="throwsAdvice"/> <idref bean="introduction"/> </list> </property> </bean> </beans> //Solution.java import aop.Person; import aop.Singer; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Object proxy = context.getBean("proxy"); try { ((Person) proxy).read("Hello!"); ((Singer) proxy).sing(""); } catch (Throwable throwable) { System.out.println(throwable.getMessage()); } } }
四、切面
在上述代码中,增强会被织入类的所有方法中。假如我们希望有选择地将增强织入,就需要使用切点进行目标连接点地定位了。Spring通过Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher组成,它通过ClassFilter定位至特定类,再通过MethodMatcher定位至特定方法。Spring提供了静态方法切点、动态方法切点、注解切点、表达式切点、流程切点、复合切点六种类型的切点。
由于增强既包含横切代码,又包含了部分连接点信息,所以我们通过增强生成切面。Spring使用Advisor表示切面的概念,一个切面同时包含横切代码和连接点信息。切面分为Advisor、PointcutAdvisor和IntroductionAdvisor,其中PointcutAdvisor的具体实现类如下
静态方法匹配器仅对方法的签名进行一次匹配,静态普通方法及静态正则表达式方法匹配切面代码示例如下
//Person.java package aop; public class Person { public void read(String s) { System.out.println("I‘m reading " + s); } public void sing(String s) { System.out.println("I‘m singing " + s); } } //PersonReadAdvisor.java package aop; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; import java.lang.reflect.Method; public class PersonReadAdvisor extends StaticMethodMatcherPointcutAdvisor { @Override public boolean matches(Method method, Class<?> aClass) { return method.getName().endsWith("read"); } @Override public ClassFilter getClassFilter() { return Person.class::isAssignableFrom; } } //spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.PersonAroundAdvice" id="aroundAdvice"/> <bean class="aop.PersonBeforeAdvice" id="beforeAdvice"/> <bean class="aop.PersonAfterAdvice" id="afterAdvice"/> <bean class="aop.PersonReadAdvisor" id="aroundReadAdvisor" p:advice-ref="aroundAdvice"/> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="beforeSingAdvisor" p:advice-ref="beforeAdvice" p:pattern=".*sing"/> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="afterSingAdvisor" p:advice-ref="afterAdvice" p:pattern=".*sing"/> <bean class="aop.Person" id="person"/> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:target-ref="person" p:proxyTargetClass="true"> <property name="interceptorNames"> <list> <idref bean="aroundReadAdvisor"/> <idref bean="beforeSingAdvisor"/> <idref bean="afterSingAdvisor"/> </list> </property> </bean> </beans> //Solution.java import aop.Person; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Person person = context.getBean("proxy", Person.class); person.read("Hello!"); person.sing("Goodbye!"); } }
动态方法匹配器由于调用方法的入参不同每次调用都会进行依次匹配,会对性能造成影响。动态切面代码示例如下
//ReadDynamicPointcut.java package aop; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut; import java.lang.reflect.Method; public class ReadDynamicPointcut extends DynamicMethodMatcherPointcut { @Override public boolean matches(Method method, Class<?> targetClass) { System.out.println("static method matcher called"); return method.getName().endsWith("read"); } @Override public boolean matches(Method method, Class<?> aClass, Object... objects) { System.out.println("dynamic method matcher called"); return method.getName().endsWith("read"); } @Override public ClassFilter getClassFilter() { return Person.class::isAssignableFrom; } } //spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dynamicAdvisor"> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> <property name="pointcut"> <bean class="aop.ReadDynamicPointcut"/> </property> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="dynamicAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans>
流程切点代表由某个方法直接或间接发起调用的其他方法。将特定类的所有方法交由代理类的一个方法,之后在运行期判断方法调用栈堆中的方法是否满足流程切点的要求。流程切面和动态切面一样会对性能造成影响。流程切面代码示例如下
//PersonDelegate.java package aop; public class PersonDelegate { private Person person; public void doAll(String s) { person.read(s); person.sing(s); } public void setPerson(Person person) { this.person = person; } } //spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="controlFlowAdvisor"> <property name="pointcut"> <bean class="org.springframework.aop.support.ControlFlowPointcut"> <constructor-arg index="0" value="aop.PersonDelegate"/> <constructor-arg index="1" value="doAll"/> </bean> </property> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="controlFlowAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans> //Solution.java import aop.Person; import aop.PersonDelegate; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Person person = context.getBean("proxy", Person.class); PersonDelegate delegate = new PersonDelegate(); delegate.setPerson(person); delegate.doAll("Hello!"); person.read("Goodbye!"); person.sing("Goodbye!"); } }
切面的定义中只有一个切点,有时会不足以描述目标连接点的信息。假如我们希望在代理类中被调用的特定方法才织入增强,这样由两个或以上单独切点确定的为复合切点。复合切面代码示例如下
//SingComposablePointcut.java package aop; import org.springframework.aop.Pointcut; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.ControlFlowPointcut; import org.springframework.aop.support.NameMatchMethodPointcut; public class SingComposablePointcut extends ComposablePointcut { public Pointcut getIntersectionPointcut() { ComposablePointcut composablePointcut = new ComposablePointcut(); ControlFlowPointcut controlFlowPointcut = new ControlFlowPointcut(PersonDelegate.class, "doAll"); NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut(); nameMatchMethodPointcut.addMethodName("sing"); return composablePointcut.intersection((Pointcut) controlFlowPointcut).intersection((Pointcut) nameMatchMethodPointcut); } } //spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.SingComposablePointcut" id="composablePointcut"/> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="composableAdvisor" p:pointcut="#{composablePointcut.intersectionPointcut}"> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="composableAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans>
引介切面是引介增强的封装器,引介切面示例如下
//Poet.java package aop; public interface Poet { void write(String s); } //PersonIntroduction.java package aop; import org.springframework.aop.support.DelegatingIntroductionInterceptor; public class PersonIntroduction extends DelegatingIntroductionInterceptor implements Poet { @Override public void write(String s) { System.out.println("I‘m writing " + s); } } //spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.aop.support.DefaultIntroductionAdvisor" id="introductionAdvisor"> <constructor-arg> <bean class="aop.PersonIntroduction"/> </constructor-arg> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="introductionAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans> //Solution.java import aop.Person; import aop.Poet; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Object obj = context.getBean("proxy"); ((Poet) obj).write("Hello!"); ((Person) obj).read("Goodbye!"); ((Person) obj).sing("Goodbye!"); } }
五、自动代理
通过ProxyFactoryBean创建织入切面的代理时,每个需要被代理的Bean都使用单独的ProxyFactoryBean进行配置。虽然可以使用父子<bean>来进行简化,但在Bean的数量较多时配置依然繁琐。为此Spring通过BeanPostProcessor接口提供了自动代理机制,让容器自动生成代理而无需手动配置。其实现类如下
通过Bean配置名进行自动代理代码示例如下
//spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.Person" id="person1"/> <bean class="aop.Person" id="person2"/> <bean class="aop.PersonAroundAdvice" id="aroundAdvice"/> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" p:beanNames="person1,person2" p:interceptorNames="aroundAdvice" p:optimize="true"/> <!-- Or beanNames="person*" --> </beans> //Solution.java import aop.Person; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Person person1 = context.getBean("person1", Person.class); Person person2 = context.getBean("person2", Person.class); person1.read("Hello!"); person2.sing("Goodbye!"); } }
通过Advisor进行自动代理代码示例如下
//spring-config.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.Person" id="person1"/> <bean class="aop.Person" id="person2"/> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <bean class="aop.PersonReadPointcut"/> </property> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> </beans>
标签:point 支持 his orm 如何 列表 prope icm 实现
原文地址:https://www.cnblogs.com/arseneyao/p/9613289.html