1、什么是AOP
Aspect Oriented Programming, 面向切面编程
面向切面编程,一句话解释:对哪些类的哪些方法,在什么时候,做哪些增强。
2、AOP的作用
方法的增强。
假如有一个代码,里面有10000个方法,现如今需要修改,例:每个方法里面加一个事务。那需要手写10000次吗?肯定不现实。
public class UserServiceImpl implements UserService{
@Override
public void create() {
System.out.println("创建一个用户");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
}
3、办法
3.1 手动写(非代理)
显然不现实
public class UserServiceImpl2 implements UserService{
private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void create() {
System.out.println("创建一个用户");
System.out.println(format.format(new Date()));
}
@Override
public void delete() {
System.out.println("删除一个用户");
System.out.println(format.format(new Date()));
}
}
3.2 代理
什么叫代理?
简单讲就是一个中间人。
public class UserServiceProxyImpl implements UserService {
private UserService userService;
private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public UserServiceProxyImpl(UserService userService) {
this.userService = userService;
}
@Override
public void create() {
userService.create();
System.out.println(format.format(new Date()));
}
@Override
public void delete() {
userService.delete();
System.out.println(format.format(new Date()));
}
}
//UserServiceProxyImpl 就相当于是中间人,Client里面就可以使用这个代理
3.3动态代理
动态(运行时在内存的操作),简单点说就是,它会帮你把以前的方法写好,自己只需要敲需要新添加的方法。
3.3.1 JDK 动态代理
/**
*Proxy.newProxyInstance 创建一个新的代理实例
*ClassLoader loader,
*Class<?>[] interfaces, 所需要代理的接口
*InvocationHandler
*
*/
public class DtproxyImpl {
private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static UserService getUserService (final UserService userService){
return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//刚开始会有疑问。为什么这里method可以直接用invoke这个方法,其实这个地方已经封装好了。
//原方法
Object res = method.invoke(proxy, args);
//需要加的方法
System.out.println(format.format(new Date())+method.getName());
return res;
}
});
}
}
原理:它会生成一个新的类
public final class $Proxy0 extends Proxy implements UserService
所以从这里可以看出它必须有接口,才能进行动态代理,因为Proxy0 必须继承Proxy ,而java是单继承的。
public final void create() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m6 = Class.forName("day5.noproxy.UserService").getMethod("updata");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("day5.noproxy.UserService").getMethod("delete");
m3 = Class.forName("day5.noproxy.UserService").getMethod("get");
m5 = Class.forName("day5.noproxy.UserService").getMethod("create");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
这里可以看出,JDK代理它用了反射。再回调了invoke方法,这个时候参数也变了。
extends Proxy 所以这里是this,所以前面参数有用,不是没用到。
假如:这里不实现接口会有什么影响?
那就要new 一个新的对象去点这些方法,若是实现接口只要换个名字。
3.3.2 cglib动态代理
/**
* Enhancer.create()
* 参数
* Class type,
* Callback callback,Ctrl进去是空的接口,看它的子类
*
*/
public class CgClibProxyImpl {
private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static UserService getUserService(UserService userService){
return (UserService) Enhancer.create(userService.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object invoke = methodProxy.invoke(userService, objects);
System.out.println(format.format(new Date())+"zzzz");
return invoke;
}
});
}
}
同样的道理,这个地方也是封装好了的。
public class UserServiceImpl$$EnhancerByCGLIB$$5ced208f extends UserServiceImpl implements Factory
它继承了UserService的实现类,不是实现的接口,所以它的优势很明显,cglib可以增强无接口实现的方法。
public final void get() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$get$0$Method, CGLIB$emptyArgs, CGLIB$get$0$Proxy);
} else {
super.get();
}
}
//-------------------------------------------------------------
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1949372272:
if (var10000.equals("updata()V")) {
return 10;
}
break;
case -1870561232:
if (var10000.equals("CGLIB$findMethodProxy(Lorg/springframework/cglib/core/Signature;)Lorg/springframework/cglib/proxy/MethodProxy;")) {
return 17;
}
break;
case -1860420502:
if (var10000.equals("CGLIB$clone$7()Ljava/lang/Object;")) {
return 26;
}
break;
这底层实现原理代码可以看出来:
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
1、它同样回调了这个方法(intercept),所以代码里面看起来没用到的Object o底层也用到了。
2、它不是用的反射,他是直接得到的。
3.3.3 两种动态代理的区别
1、JDK动态代理只能代理接口实现的方法,而cglib不是。
2、JDK底层用的是反射,而cglib是直接去取的。
4、SpringAOP
4.1 基于接口(xml,接口)
是因为他的"什么时候"是要实现接口,所以叫基于接口。
代理需要什么?
1、谁被代理了,被代理实例。
2、被代理的接口
3、新添加的方法(代理通知)
xml里面写的bean:
<?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">
<!--被代理bean-->
<bean id="userService" class="day5.noproxy.UserServiceImpl"/>
<!-- 代理通知bean -->
<bean id="logAdvice" class="day5.springAop.LogAdvice"/>
<bean id="logAdvice2" class="day5.springAop.LogAdvice2"/>
<!-- 代理工厂bean -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--被代理接口-->
<property name="proxyInterfaces">
<list>
<value>day5.noproxy.UserService</value>
</list>
</property>
<!--被代理bean-->
<property name="target" ref="userService"/>
<!--通知-->
<property name="interceptorNames">
<list>
<value>logAdvice</value>
<value>logAdvice2</value>
</list>
</property>
</bean>
</beans>
这里是使用的xml和接口bean,是对整个被代理bean里面的方法进行了方法的增强,那是否能够只对其中一个或几个方法来进行增强呢?当然可以。
<?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">
<!--被代理bean-->
<bean id="userService" class="day5.noproxy.UserServiceImpl"/>
<!-- 代理通知bean -->
<bean id="logAdvice" class="day5.springAop.advic.LogAdvice"/>
<bean id="logAdvice2" class="day5.springAop.advic.LogAdvice2"/>
<bean id="advior" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="logAdvice"/>
<property name="mappedNames" value="get,updata"/>
</bean>
<!-- 代理工厂bean -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--被代理接口-->
<property name="proxyInterfaces">
<list>
<value>day5.noproxy.UserService</value>
</list>
</property>
<!--被代理bean-->
<property name="target" ref="userService"/>
<!--通知-->
<property name="interceptorNames">
<list>
<!-- <value>logAdvice</value>-->
<!-- <value>logAdvice2</value>-->
<value>advior</value>
</list>
</property>
</bean>
</beans>
前面Client下使用需要getBean 接口 , 那是否能不用getBean接口,直接getBean被代理类呢?当然可以
<?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">
<!--被代理bean-->
<bean id="userService" class="day5.noproxy.UserServiceImpl"/>
<!-- 代理通知bean -->
<bean id="logAdvice" class="day5.springAop.advic.LogAdvice"/>
<bean id="logAdvice2" class="day5.springAop.advic.LogAdvice2"/>
<bean id="advior" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="logAdvice2"/>
<property name="mappedNames" value="get,delete"/>
</bean>
<!-- 代理工厂bean -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Service"/>
<!--通知-->
<property name="interceptorNames">
<list>
<!-- <value>logAdvice</value>-->
<!-- <value>logAdvice2</value>-->
<value>advior</value>
</list>
</property>
</bean>
</beans>
代理工厂代码太多了,能够优化呢?也可以
<?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">
<!--被代理bean-->
<bean id="userService" class="day5.noproxy.UserServiceImpl"/>
<!-- 代理通知bean -->
<bean id="logAdvice" class="day5.springAop.advic.LogAdvice"/>
<bean id="logAdvice2" class="day5.springAop.advic.LogAdvice2"/>
<bean id="advior" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="logAdvice"/>
<property name="mappedNames" value="get,delete"/>
</bean>
<!-- 代理工厂bean -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
4.2基于标签
为什么说是基于标签呢?
因为他需要在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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--被代理bean-->
<bean id="userService" class="day5.noproxy.UserServiceImpl"/>
<!-- 代理通知bean -->
<bean id="logAdvice" class="day6.LabelAOP.LogAdvice"/>
<aop:config>
<aop:aspect ref="logAdvice">
<aop:pointcut id="pointcut" expression="
execution(* day5.noproxy.UserServiceImpl.create(..))"/>
<aop:before method="logDate" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>