1:首先我们来简单了解一下spring的概念
它是一个 full-stack 框架,提供了从表现层到业务层再到持久层的一套完整的解决方案。我们在项目中可以只使用 spring 一个框架,
它就可以提供表现层的 mvc 框架,持久层的 Dao 框架。它的两大核心 IoC 和 AOP 更是为我们程序解耦和代码简洁易维护提供了支持
当然他还有一个不是功能的功能及集成其他框架(当年面试官问我的时候怎么也没想到…)。
2 本文将以spring aop的代理模式来介绍spring aop来介绍其原理及在日常工作中的使用。
2.1 首先介绍一下aop模块
AOP模块用于我们的Spring应用做面向切面的开发, 很多支持由AOP联盟提供,这样就确保了Spring和其他AOP框架的共通性。这个模块将元数据编程引入Spring。
2.2 aop原理
其实spring aop 主要采用了代理模式来实现面向切面的。整个spring的实现共使用了9种设计模式这里仅仅列举一下不做具体讲解
1.简单工厂
2. 工厂方法(Factory Method)
3. 单例(Singleton)(这个要着重掌握)
4. 适配器(Adapter)
5.包装器(Decorator)
6. 代理(Proxy)(我们今天的主角)
7.观察者(Observer)、
8. 策略(Strategy)
9.模板方法(Template Method)
什么是代理模式:代理模式的核心作用就是通过代理,控制对对象的访问。
它的设计思路是:定义一个抽象角色,让代理角色和真实角色分别去实现它。
(通俗的讲就是本来是自己的事 你不想做了交给别人去做了,列如 你把游戏交给代练)
2.3 代理模式分静态代理和动态代理
/**
* Created by hyl
* on 2020/4/23.
* 定义一个我的游戏接口
*/
public interface Game {
/**
* 定义 玩游戏的方法
*/
void playGame();
}
/**
* Created by hyl
* on 220/4/23.
* 定义一个我自己玩游戏的类
*/
public class MyGame implements Game {
//实现我自己的玩游戏的方法
@Override
public void playGame() {
//简单举例不做业务
System.out.println("我要陪女朋友我没时间玩....");
}
}
/**
* Created by hyl
* on 2020/4/23.
* 定义代理游戏对象
* 既然我想让你帮我代练 你肯定的会打游戏 所以也要实现 游戏接口
*/
public class ProxyGame implements Game {
//给一个接口 用来接收真实玩游戏的人
private Game game;
//通过构造的方法置入要被代理的对象
public ProxyGame(Game game) {
this.game = game;
}
@Override
public void playGame() {
System.out.println("我游戏代理");
game.playGame();
System.out.println("我游戏代理,我帮你打游戏");
}
}
/**
* Created by hyl
* on 2020/4/23.
*/
public class TestMain {
public static void main(String []ages){
Game myGame = new MyGame();
Game proxyGame = new ProxyGame(myGame);
proxyGame.playGame();
}
}
从上面得例子我们可以看到静态代理到过于死板当然我们能看的出来java的设计者或者大牛们肯定也能看出来。那么就有了动态代理
2.动态代理:jdk动态代理(java提供的),CGLB代理
jdk动态代理:
动态代理不需要我们去创建代理类,那我们只需要编写一个动态处理器就可以了。真正的代理对象由 JDK 在运行时为我们动态的来创建。
以上面的例子理解就是:我们不想打游戏了还得去找一个代理帮忙还得确认代理会不会打游戏这很麻,于是平台给我们提供了一个功能你把账号给平台平台来帮你完成即可。
代码举例:
/**
* Created by hyl
* on 2020/4/23.
* 平台给我们提供的代理
*/
public class JdkProxyHandler {
/**
* 用来接收真实对象
*/
private Object game;
/**
* 通过构造方法传进来真实对象
*/
public JdkProxyHandler(Game game) {
super();
this.game = game;
}
/**
* 给真实对象生成一个代理对象实例
*
* @return Object
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(game.getClass().getClassLoader(),
game.getClass().getInterfaces(), (proxy, method, args) -> {
System.out.println("我游戏代理");
// 赔女朋要自己来 这事还是自己干的好.....
Object object = method.invoke(game, args);
System.out.println("我游戏代理,我帮你打游戏");
return object;
});
}
}
public class TestMain {
public static void main(String []ages){
/* Game myGame = new MyGame();
Game proxyGame = new ProxyGame(myGame);
proxyGame.playGame();*/
Game myGame = new MyGame();
// 创建一个代理对象实例
Game proxy = (Game) new JdkProxyHandler(myGame).getProxyInstance();
proxy.playGame();
}
}
从上面的例子我们可以看到jdk要实现动态代理虽然不需要有代理对象我们可以使用反射来完成但是必须需要我们自己定义一个接口这样代码的耦合性还是很高。
这样还是不是很方便万一我又有一个女朋友不想那个陪现在的女朋友了怎么办,在找个人来陪女朋友这也太麻烦了。有没有一个平台就给我们搞定这所有的事呢,显然是有的CGLIB
CGLIB 代理:采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
注:这里有一个点就是因为采用的是继承,所以不能对final修饰的类进行代理。
代码举例:
/ **
* Created by hyl
* on 2020/4/23.
* CGLIB 代理
*/
public class CglibProxyHandler implements MethodInterceptor {
/**
* 真实被代理的对象
*/
private Object target;
public Object getProxyInstance(final Object target) {
this.target = target;
// Enhancer类是CGLIB中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
Enhancer enhancer = new Enhancer();
// 将被代理的对象设置成父类
enhancer.setSuperclass(this.target.getClass());
// 回调方法,设置拦截器
enhancer.setCallback(this);
// 动态创建一个代理类
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("我游戏代理");
// 唱歌需要明星自己来唱
Object result = methodProxy.invokeSuper(object, args);
System.out.println("我游戏代理,我帮你打游戏");
return result;
}
}
//同时我们在MyGame里面在添加一个方法
//女朋的使用
@Override
public void use() {
System.out.println("女人只会影响我拔刀的速度,交给代理去.....");
}
public class TestMain {
public static void main(String []ages){
/* Game myGame = new MyGame();
Game proxyGame = new ProxyGame(myGame);
proxyGame.playGame();*/
/* Game myGame = new MyGame();
// 创建一个代理对象实例
Game proxy = (Game) new JdkProxyHandler(myGame).getProxyInstance();
proxy.playGame();*/
MyGame myGame = new MyGame();
MyGame proxy = (MyGame) new CglibProxyHandler().getProxyInstance(myGame);
proxy.playGame();
proxy.use();
}
}
从以上代码上来看代理类给我们代码没有一点关系从而降低了代码的耦合性同时他也可以给我们代码添加任何方法。
简单总结一下两种动态代理:
CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多(因为他采用字节码创建)。所以对于单例的对象,因为无需频繁创建对象,
用 CGLIB 合适,反之使用JDK方式要更为合适一些
4:那么spring aop 使用哪种代理呢?我们打开spring 5.1.4源码看一下
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
public DefaultAopProxyFactory() {
}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config); //jdk动态代理
} else {
Class targetClass = config.getTargetClass();
if(targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
//用3目运算符判断是jdk动态代理还是cglb的动态代理。
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass)?new ObjenesisCglibAopProxy(config):new JdkDynamicAopProxy(config));
}
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class[] ifcs = config.getProxiedInterfaces();
return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
}
}
对上面代码上总结:Spring AOP 中的代理使用:如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;如果目标对象没有实现了接口,则采用 CGLIB 。
扩展一下:
讲完AOp代理我们在简单了解一下我们在日常工作中最常见的一个问题 在多线程事务环境下我们都是这样保证线程安全及事务的:
@Override
@Transactional(rollbackFor = {Exception.class, RuntimeException.class})
public synchronized Long saveaccountEpidemicwithdraw(@RequestBody AccountEpidemicWithdrawDto accountWithdrawDto) {
//这里写我们的事务逻辑
}
看完aop的代理实现后有没有觉得这一个写法有问题。答案是现显然有问题 这样绝对不能保证多线的安全。
至于为什么能呢 因为@Transactional 这个标签会使用aop给我们生成代理类大致描述一下,具体请参看spring的TransactionAspectSupport类里面的 invokeWithinTransaction()这个方法,
{
1.开启事务
2.调用们定义的原方法 saveaccountEpidemicwithdraw();
3.关闭事务
}
看到这里应该明白为什么线程还是不安全的了吧。因为事务的作用域大于了synchronized的作用域及 事务的周期是1-3步而synchronized的作用域只有2。那么就会出现当一个线程结束2的还未执行
3步骤的时候另个线程就进来了读到原来的数据造成脏数据及线程不安全 所以日程工作总我们避免这样写。
知道了问题就知道怎么解决 我们让 synchronized作用域包涵 Transactional的作用域即可但是在非事务的方法里面不要做与事务相关的及操作 不然整个事务不会生效
简单总结就是在非事务方法里面发起对事务方法的调用除事务方法覆盖的操作起效其他操作没有事务。
列如(saveaccountEpidemicwithdraw 把这个方法上面的关键字synchronized去掉):
public synchronized Long synSaveaccountEpidemicwithdraw(@RequestBody AccountEpidemicWithdrawDto accountWithdrawDto){
this.saveaccountEpidemicwithdraw();
}
注: spring 还规定事务方法要以public ,static修饰的方法不能被代理因为static修饰的方法不属于任何对象只属于类。