转载请注明出处,否则侵权必究
-
Q:什么是AOP?
AOP:Aspect Oriented Programming,面向切面编程
还有POP面向过程(C,C++),OOP面向对象(JAVA),IOP面向接口
-
Q:什么是切面?
把业务主流程比成一个纵面的话,那么一些与主业务无关的功能作为横切面插入系统各处,称之为切面。
我们需要知道的还有:
连接点(Joinpoint):程序执行的某个特殊位置,如类初始化前,方法调用前。即切面可以切入的点
Spring仅支持方法的连接点,既仅能在方法调用前,方法调用后,方法抛出异常时等这些程序执行点进行织入增强。切点(Pointcut):就spring来说,每个方法都有很多连接点,但是只有某个连接点满足指定要求时候,才能成为切点,即切面真正切入的地方。
如何使用表达式定义切入点,是AOP的核心,Spring默认使用AspectJ切入点语法。增强(Advice):即在切点做的一些事情,有“around”,“before”,“after”等类型
目标对象(Target) :被AOP框架进行增强处理的对象
-
Q:AOP的工作流程是什么样的?
AOP是把已经编译好的一段代码拿过来,在前面或后面执行一些操作,然后合并成一个文件,编译出来。
-
Q:AOP是如何配置的?
spring的配置文件
-
Q:execution表达式?
例: execution (* com.sample.service..*. *(..))
1、execution()::表达式主体。
2、第一个*号:表示返回类型, *号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
-
Q:AOP有几种通知方式?
前置通知:在我们执行目标方法之前运行(@Before)
后置通知:在我们目标方法运行结束之后,不管有没有异常(@After)
返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)
异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
环绕通知:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。
-
Q:当一个方法被多个切面切入时,执行顺序?
aspect1 和 aspect2 的执行顺序也是未定的
如何指定每个 aspect 的执行顺序
- 实现org.springframework.core.Ordered接口,实现它的getOrder()方法
- 给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order
不管采用上面的哪种方法,都是值越小的 aspect 越先执行。
@Component
@Aspect
@Order(1)
public class FirstAspect{
@Before(value="execution(* com.xj.mvc.service..*.*(..))")
public void befor(){
System.out.println("firstAspect");
}
}
@Component
@Aspect
public class FirstAspect implements Ordered{
@Before(value="execution(* com.xj.mvc.service..*.*(..))")
public void befor(){
System.out.println("firstAspect");
}
public int getOrder() {
return 1;
}
-
Q:AOP的实现原理?
动态代理
-
Q:什么叫代理?
Proxy
通俗的说,让别人帮助你做你并不关心的事情,叫做代理,生活中常见的如中介
代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用
代理模式的通用类图
Subject:抽象主题角色,可以是抽象类,也可以是接口
RealSubject:具体主题角色,也叫做被委托角色或被代理角色,是业务逻辑的具体执行者
Proxy:代理主题角色,也叫做委托类或代理类,负责对真实角色的应用,把所有抽象主题定义的方法委托给真实主题角色实现
(太拗口了!)
举个例子:我们通过手机APP买火车票,我们知道APP并不卖票,只有火车站才真正卖票,APP只是代理,APP买的票其实是到火车站买的
那么,我们就是客户,APP就是代理角色,火车站是具体主题角色,卖票称为抽象主题角色
/**
* 抽象主题角色
*/
public interface ISubject {
//买票
void buyTicket();
}
/**
* 具体主题角色
*/
public class RealSubjectImpl implements ISubject {
//去火车站售票点买
@Override
public void buyTicket() {
System.out.println("买火车票!");
}
}
/**
* 代理角色 APP
*/
public class ProxySubjectImpl implements ISubject {
private ISubject real;
//构造注入,注入具体主题类,构造函数传入的是接口类型的参数,是因为不知道具体的具体主题类是哪一个,所以传一个接口类型的
public ProxySubjectImpl(ISubject real) {
this.real = real;
}
public void before(){
System.out.println("app付钱!");
}
@Override
public void buyTicket() {
before();
this.real.buyTicket();
}
}
public class ProxyTest {
/**代理模式
* @param args
*/
public static void main(String[] args) {
//实现了APP代理买火车票
ISubject sub = new ProxySubjectImpl(new RealSubjectImpl());
sub.buyTicket();
}
}
打印结果:
app付钱!
买火车票!
-
Q:什么叫静态代理?与动态代理的区别
动态代理和静态代理都是对目标方法进行增强,而且让增强的动作和目标动作分开,达到解耦的目的
静态代理:
显示声明代理对象,在编译期间就生成了代理类
由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行之前,代理类的类文件就已经创建好了
优点:简单,实用,高效
缺点:
1.由于静态代理中的代理类是针对某一个类去做代理的,假设系统中有一百个service,则需要创建100个代理类
2.如果一个service中有很多方法需要事务,发现代理对象中的方法有很多重复代码。
即重用性不强
动态代理:
在程序运行时通过反射机制动态的创建代理类
优点:灵活,减少代码冗余
缺点:效率较低
-
Q:动态代理的实现
- 基于JDK实现:
jdk动态代理是JRE提供给我们的类库,可以直接使用,不依赖第三方
利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法
//接口 抽象主题角色
public interface PersonService {
public String savePerson();
public void upodatePerson();
public void deletePerson();
}
//具体主题角色
public class PersonServiceImpl implements PersonService{
@Override
public String savePerson() {
System.out.println("添加");
return "保存成功!";
}
@Override
public void upodatePerson() {
System.out.println("修改");
}
@Override
public void deletePerson() {
System.out.println("删除");
}
}
//增强类
public class MyTransaction {
public void beginTransaction(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
}
//动态代理类
public class PersonServiceInterceptor implements InvocationHandler {
//目标类
private Object target;
//增强类
private MyTransaction myTransaction;
//构造函数注入目标类和增强类
public PersonServiceInterceptor(Object target, MyTransaction myTransaction) {
this.target = target;
this.myTransaction = myTransaction;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.myTransaction.beginTransaction();
Object returnValue = method.invoke(this.target,args);
this.myTransaction.commit();
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
//目标类 具体主题角色
Object target = new PersonServiceImpl();
//增强类
MyTransaction myTransaction = new MyTransaction();
//组装
PersonServiceInterceptor interceptor = new PersonServiceInterceptor(target,myTransaction);
PersonService personService = (PersonService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),interceptor);
personService.upodatePerson();
System.out.println(personService instanceof Proxy);
}
}
//运行结果
开启事务
修改
提交事务
true
使用内置的Proxy实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理
如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。
- CGLIB代理
强制使用CGlib
是cglib的jar包实现的
cglib动态代理产生的代理对象是目标对象的子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
代码就不贴了,就是Proxy动态代理类实现MethodInterceptor接口(net.sf.cglib.proxy.MethodInterceptor),重写intercept方法调用methodProxy.invoke(targetObject, args)方法
- 总结
1.JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
2.JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。