AOP前情之代理模式
静态代理
角色分析:
- 抽象角色:一般会用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些相关业务的处理
- 客户:访问代理对象的人
模拟静态代理
现有service业务是增删改查
Service接口
package com.lwh.service;
public interface Service {
public void add();
public void delete();
public void update();
public void query();
}
service接口实现类
package com.lwh.service;
public class ServiceImpl implements Service{
@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("查询方法");
}
}
客户端
package com.lwh.service;
public class Client {
public static void main(String[] args) {
ServiceImpl service=new ServiceImpl();
service.add();
}
}
现在需要实现其它功能,我需要增加一些其它业务,在每次执行方法之前打印日志,但是我不能修改代码,在公司中,修改代码是禁忌,所以我需要使用到代理模式
我们只需要新增一个代理类,ProxyServletImpl
package com.lwh.service;
public class ProxyServletImpl implements Service{
private ServiceImpl service;
public ServiceImpl getService() {
return service;
}
public void setService(ServiceImpl service) {
this.service = service;
}
@Override
public void add() {
log("add");
service.add();
}
@Override
public void delete() {
log("delete");
service.delete();
}
@Override
public void update() {
log("update");
service.update();
}
@Override
public void query() {
log("query");
service.query();
}
public void log(String asg)
{
System.out.println("使用了"+asg+"方法");
}
}
我们并没有更改原有的代码,我们采用的是new一个新对象,在我们代理类里面实现自己的方法加上原有的service逻辑,成功的实现的需要的功能,这就是代理的好处。
动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理可以分为两大类:
- - 基于接口的动态代理,例如:JDK动态代理 - 基于类的动态代理,例如:cglib
AOP基础之模拟动态代理实现
写动态代理工具类
- InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
- 每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用
- Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法,分别有以下几个参数
- - loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载 - interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。 - h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
package com.lwh.demo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的真实对象
private Object target;
//为了通用性,使用set方法实现真实对象的注入
public void setTarget(Object target) {
this.target = target;
}
//获得代理实现类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());//传入执行的方法名
Object result=method.invoke(target, args);
return result;
}
//加入的其它业务逻辑
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
客户端Client
package com.lwh.demo;
import com.lwh.service.Service;
import com.lwh.service.ServiceImpl;
public class Client {
public static void main(String[] args) {
ServiceImpl service=new ServiceImpl();
ProxyInvocationHandler proxyInvocationHandler=new ProxyInvocationHandler();
proxyInvocationHandler.setTarget(service);
Service proxy = (Service) proxyInvocationHandler.getProxy();
proxy.add();//输出 执行了add方法 增加方法
}
}
我们可以这么理解,在客户端运行的过程中,我们传入了ServiceImpl,自动生成了动态代理类,动态代理类自动实现了我们传入的真实类的继承接口,这样我们可以使用接口中定义的所有方法,当我们想用动态代理类时,直接使用后会调用proxyInvocationHandler的invoke方法,执行我们所加入的代码逻辑,实现业务的拓展!
动态代理好处:
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务(因为传入的就是一个真实对象)
- 一个动态代理类可以代理多个类,只要是实现了同一个接口就行,因为时动态生成的类,所以可以生成多个。
AOP
什么是AOP
AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Spring的AOP实现
先解释几个官方名词:
- 横切关注点:跨越应用程序多个模块的方法或者功能,即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,如日志,安全,缓存,事务等等
- 切面:横切关注点被模块化的特殊对象,他就是一个类
- 通知:切面必须完成的工作,也就是类中的一个方法
- 目标:被通知的对象
- 代理:向目标对象应用通知后创建的对象
- 切入点:切面通知执行的地点的定义
- 连接点:与切入点匹配的 执行点
创建子项目,增加maven:
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
修改beans.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-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
</beans>
方式一、使用Spring提供的API接口
- 先写Service接口
package com.lwh.service;
public interface Service{
public void add();
public void delete();
public void update();
public void query();
}
- 再写真实对象
package com.lwh.service;
public class ServiceImpl implements Service{
@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("使用了查询方法");
}
}
- 写Log类
在方法执行前进行操作,需要继承MethodBeforeAdvice接口
package com.lwh.log;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
/** *继承了MethodBeforeAdvice后,在before方法中写的逻辑会在真实对象执行前调用 * @param method:代表要执行的目标对象的方法 * @param objects:代表是参数 * @param o:代表目标对象 * @throws Throwable */
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
在方法执行后进行操作,并且操作返回值的,需要继承AfterReturningAdvice接口
package com.lwh.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
/** * AfterReturningAdvice会在返回后调用,可以拿到真实对象的返回值 * @param returnValue * @param method * @param args * @param target * @throws Throwable */
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"返回结果为"+returnValue);
}
}
- 注册beans.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-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="userService" class="com.lwh.service.ServiceImpl"/>
<bean id="blog" class="com.lwh.log.BeforeLog"/>
<bean id="alog" class="com.lwh.log.AfterLog"/>
<aop:config>
<!--切入点,也就是我们需要在哪个地方执行,execution(要执行的参数)-->
<!--com.lwh.service.ServiceImpl这句话代表的是ServiceImpl下的所有方法被代理;*(..)代表的是可以传入任意参数-->
<aop:pointcut id="pointcut" expression="execution(* com.lwh.service.ServiceImpl.*(..))"/>
<!--执行环绕增加,就是将这下面配置的类切入到我上面配置的路径中-->
<aop:advisor advice-ref="alog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="blog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 编写测试类
import com.lwh.service.Service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//动态代理代理的是接口,直接声明接口类接受就行
Service service=context.getBean("userService",Service.class);
service.add();
/** * 输出: * com.lwh.service.ServiceImpl的add被执行了 * 使用了增加方法 * 执行了add返回结果为null */
}
}
方式二、自定义来实现AOP(切面定义)
- 编写自定义切面
package com.lwh.log;
public class MyAspect {
public void beforeMethod(){
System.out.println("========方法执行前=========");
}
public void afterMethod(){
System.out.println("========方法执行后=========");
}
}
- 修改beans.xml
<bean id="myaspect" class="com.lwh.log.MyAspect"/>
<aop:config>
<aop:aspect ref="myaspect">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.lwh.service.ServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="beforeMethod" pointcut-ref="point"/>
<aop:after method="afterMethod" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
- 测试类
import com.lwh.service.Service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//动态代理代理的是接口,直接声明接口类接受就行
Service service=context.getBean("userService",Service.class);
service.add();
/** * 输出: * ========方法执行前========= * 使用了增加方法 * ========方法执行后========= */
}
}
方式三、用注解实现
- 在Service接口中增加一个方法 public String print();
增加真实对象类的实现 <pre>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> <span class="token string">"打印文字"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
- 增加代理类AnnotationPointCut.java
- 3. 现在类名上注解Aspect,表示这是一个切面 3. 用地多的有以下三个注解
- Test类 <pre>
<span class="token keyword">import</span> <span class="token namespace">com<span class="token punctuation">.</span>lwh<span class="token punctuation">.</span>service<span class="token punctuation">.</span></span><span class="token class-name">Service</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>context<span class="token punctuation">.</span></span><span class="token class-name">ApplicationContext</span><span class="token punctuation">;</span>
- 3. @Before,代表在方法执行前,内有参数代表切入点,和xml配置一样,需先用execution表达式来设置值 3. @After,代表方法执行后,返回值之后,可以理解为最后面 3. @Around,代表环绕,与其它一样需要设置切入点,其中需要传入一个参数ProceedingJoinPoint这个是Spring自动生成的,它里面有一个proceed方法表示方法执行后的返回值,我们用一个Object来接受,再打印结果
</ol> 思考:输出顺序是怎么样的? <pre> <span class="token keyword">package</span> <span class="token namespace">com<span class="token punctuation">.</span>lwh<span class="token punctuation">.</span>log</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>aspectj<span class="token punctuation">.</span>lang<span class="token punctuation">.</span></span><span class="token class-name">ProceedingJoinPoint</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>aspectj<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span></span><span class="token class-name">After</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>aspectj<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span></span><span class="token class-name">Around</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>aspectj<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span></span><span class="token class-name">Aspect</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>aspectj<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span></span><span class="token class-name">Before</span><span class="token punctuation">;</span>
<span class="token annotation punctuation">@Aspect</span> <span class="token comment">//标志这这个类是一个切面</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AnnotationPointCut</span> <span class="token punctuation">{
<!-- --></span>
<span class="token annotation punctuation">@Before</span><span class="token punctuation">(</span><span class="token string">"execution(* com.lwh.service.ServiceImpl.*(..))"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">before</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{
<!-- --></span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"===========方法执行前============"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token annotation punctuation">@After</span><span class="token punctuation">(</span><span class="token string">"execution(* com.lwh.service.ServiceImpl.*(..))"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">after</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{
<!-- --></span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"==========方法执行后============="</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token annotation punctuation">@Around</span><span class="token punctuation">(</span><span class="token string">"execution(* com.lwh.service.ServiceImpl.*(..))"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">around</span><span class="token punctuation">(</span><span class="token class-name">ProceedingJoinPoint</span> jp<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Throwable</span> <span class="token punctuation">{
<!-- --></span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"环绕前"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Object</span> proceed <span class="token operator">=</span> jp<span class="token punctuation">.</span><span class="token function">proceed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>proceed<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"环绕后"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>context<span class="token punctuation">.</span>support<span class="token punctuation">.</span></span><span class="token class-name">ClassPathXmlApplicationContext</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token punctuation">{
<!-- --></span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{
<!-- --></span>
<span class="token class-name">ApplicationContext</span> context <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ClassPathXmlApplicationContext</span><span class="token punctuation">(</span><span class="token string">"beans.xml"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//动态代理代理的是接口,直接声明接口类接受就行</span>
<span class="token class-name">Service</span> service<span class="token operator">=</span>context<span class="token punctuation">.</span><span class="token function">getBean</span><span class="token punctuation">(</span><span class="token string">"userService"</span><span class="token punctuation">,</span><span class="token class-name">Service</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
service<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">/**输出: * 环绕前 * ===========方法执行前============ * 打印文字 * 环绕后 * ==========方法执行后============= */</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</pre>