离第一篇的spring到现在已经有将近一个星期了,迟迟没有写这篇文章的原因主要是自己还没有准备好第二篇,平时上班本来就有点忙,然后自己又想把知识点写的透彻然后最好能结合我们现在的项目(博主虽然不才,但是公司的项目好歹也是上千万的app用户量),如果能把学到spring的东西对应上项目,那就是再好不过了。然后对于spring接下来我觉得还有三个方面,第一个是spring的注解,这个其实比较简单一看就明白,我放到最后写。第二个是Aop相关的内容,我觉得东西还蛮多的。第三个是数据库操作相关的,结合基础的事务、jdbc我觉得内容也不少,数据库的部分我想合着ibatis、mybatis、数据库一起讲,最为基础知识说在最前面。那就直接来看看今天的正题Aop。
aop的原理其实就是我们的动态代理技术,动态代理也是设计模式的一种,了解动态代理前我们先来熟悉静态代理。举个简单的例子,现在我有一个接口
public interface Greeting {
void sayHello();
}
他的实现类
public class GreetingImpl implements Greeting {
public void sayHello() {
System.out.println("hello");
}
}
现在我想让这个sayHello接口能做点别的事情,这个时候我就可以用代理来实现。来看下我的写法:
public class GreetingStaticProxy implements Greeting {
GreetingImpl greetingImpl;
public GreetingProxy(GreetingImpl greetingImpl) {
this.greetingImpl = greetingImpl;
}
public void sayHello() {
before();
greetingImpl.sayHello();
after();
}
private void after() {
System.out.println("after");
}
private void before() {
System.out.println("before");
}
}
这个代理类封装了sayhello方法,这样的话我用这个代理类就扩展了当前的功能,我们看下测试类:
@Test
public void TestStaticProxy(){
GreetingStaticProxy greetingProxy=new GreetingStaticProxy(new GreetingImpl());
greetingProxy.sayHello();
}
打印的结果是
before
sayhello
after
可以看到我们通过GreetingStaticPorxy这个类扩展了GreetingImpl的功能,这就是静态代理。静态代理可以扩展类的功能,但是有一个问题是,如果接口变了,我们的代理类就咬跟着一起变,这样也太不灵活了,而且工程里会有一大堆的代理类。我们接下来看看动态代理。
动态代理有两种方式,一个是jdk自带的代理方式,还有一个是用开源的一个库cglib最动态代理,我们分别都来看一下。
我们还是用上面的Greeting和GreetingImpl这两个类,看看如何实现这个类的JDK动态代理。
public class JdkDynamicProxy implements InvocationHandler{
private Object object;
public JdkDynamicProxy(Object object) {
this.object = object;
}
public T getProxy(){
return (T)Proxy.newProxyInstance(getClass().
getClassLoader(),object.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result=method.invoke(object,args);
after();
return result;
}
private void after() {
System.out.println("after");
}
private void before() {
System.out.println("before");
}
}
简单介绍下getProxy()是封装给外部获取代理对象的,里面传了3个参数
1、类加载器 ClassLoader
2、这个类的所有接口类(从这个参数可以看出来,JDK代理的局限性在于,目标类必须有接口才能被代理)
3、invocationHandler对象(本类即成了他,所以传了this)
然后再看下invoke方法,method.invoke()这个是代理目标类本身调用的方法,before和after就是我们给他增强的方法,invoke里面的参数一个是目标类,还一个是目标类方法的参数。最后看我们的测试类:
@Test
public void TestJdkDynamicProxy(){
Greeting greetingProxy=new JdkDynamicProxy(new GreetingImpl()).getProxy();
greetingProxy.sayHello();
}
可以看到getProxy()返回的我们用接口来接受,因为代理对象就是会继承Greeting接口。这样打印结果就和我们预期一样了。上面也说了,JDK代理虽然能解决静态代理的问题,但是他的限制是没有接口的类没法代理,所以我们看看有木有更好的方案,这边我们来学习第二个动态代理的方式cglib。
cglib做动态代理没有任何限制,任何类都可以做代理目标类,我们还是以上面说的greeting和greetingimpl为例。
public class CglibDynamicProxy implements MethodInterceptor{
public T getProxy(Class cls){
return (T) Enhancer.create(cls,this);
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result=methodProxy.invokeSuper(o,objects);
after();
return result;
}
private void before() {
System.out.println("before");
}
private void after() {
System.out.println("after");
}
}
可以看到这就是我们cglib动态代理的写法。稍微解释下,首先我们需要maven依赖cglib,这个我们可以在网上找 直接搜索”cglib maven”就可以搜到maven仓库了。然后可以看大getproxy方法有两个参数分别是
1、class 目标类
2、methodinterceptor对象(本类继承了,所以传的this)
然后在intercept的方法里面,可以看到他是用了invokesuper这个方法,里面传了对象和参数,其实从我们这个方法可以看到一点,他为什么调用父类的方法?难道这个proxy继承了我们的目标类?是的,cglib代理的原理就是继承,他是继承了目标类去做的代理。那我们看看测试类,也比较简单
@Test
public void TestCglibDynamicProxy(){
CglibDynamicProxy proxy=new CglibDynamicProxy();
GreetingImpl greeting = proxy.getProxy(GreetingImpl.class);
greeting.sayHello();
}
如果我们不相信是继承的关系,可以去试试 proxy instanceOf greeting 如果是true那就说明是继承关系。
以上就是我们说的aop的原理,下面我们就来看看spring对aop做了哪些封装。
上面还没说aop是干啥的,我们这里先说说,有啥用这个东西。 aop叫面向切面编程,他的用途。举个例子,比如说我们想统计代码中某些方法的执行时常,那正常想法是在他执行前打印一下时间,执行后打印一下时间,写起来很简单。但是如果我们要打印的地方很多的话,我们需要一个一个的去加吗?能不能有一个东西,能如一把刀一样(我想到了庖丁解牛)可以横着把我们需要操作的方法都给他串联起来,让他们都具有这样的功能。这其实就是aop要做的事情,横向的去增强一些东西。项目中用到的有日志的打印,性能的监控、权限的控制等等。那aop在spring中其实有两种形式,一个是spring本身就自带的aop功能,还有一个是基于开源的Aspect的aop功能,当然后者更简便而且用的也更多。我们都来看下一。我们把spring自带的aop叫他传统aop。
首先理解下3种增强方式
public class GreetingBeforeAndAfter implements MethodBeforeAdvice,AfterReturningAdvice {
public void afterReturning(Object result, Method method, Object[] objects, Object target) throws Throwable {
System.out.println("after");
}
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("before");
}
}
前置增强只要继承MethodBeforeAdvice 后置增强继承AfterReurnningAdvice并实现方法就就ok了。看看怎么用
@Test
public void test(){
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new GreetingImpl());
proxyFactory.addAdvice(new GreetingBeforeAndAfter());
GreetingImpl greeting= (GreetingImpl) proxyFactory.getProxy();
greeting.sayHello();
}
settarget设置目标类,addadvice添加我们的增强就ok了。
环绕增强稍微有点不一样,一起来看一下
public class GreetingAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
before();
Object proceed = methodInvocation.proceed();
after();
return proceed;
}
private void after() {
System.out.println("after");
}
private void before() {
System.out.println("before");
}
}
继承的和cglib一样,在invoke方法里面需要调用proceed方法。测试类和上面完全一样。
我们再来看看如果在spring的配置文件里面怎么写这些增强。
<bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="greetingImpl"/>
<property name="interceptorNames" value="greetingAdvisor"/>
<property name="proxyTargetClass" value="true"/>
bean>
上面可以看到几个配置,
第一个是目标对象,这个对象需要在spring里面注册,可以用bean的方式注册,也可以用注解@component(“greetingimpl”)的方式。
第二个是增强的类的对象,这个也需要注册到spring
第三个是增强的类代理类,true表示cglib,false表示jdk的方式。
还有最上面有个注解叫织入增强,这个是相对于我们下面要说的这个引入增强来说的,织入增强是对方法的增强,而引入增强则是对类的增强。看看测试类的调用:
@Test
public void injectTest(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
GreetingImpl greeting= (GreetingImpl) ac.getBean("greetingProxy");
greeting.sayHello();
}
很容易理解,就不解释了。我们再来看看刚才说的引入增强,举个例子说明引入增强,比如一个a类,他在不想继承b类的情况下,却想去执行b类的方法,可以用引入增强去做。
现在我门增加一个类和他的实现类:
public interface Apology {
void saySorry(String name);
}
public class ApologyImpl implements Apology{
public void saySorry(String name){
System.out.println("say sorry"+name);
}
}
看看引入增强类写法,让他去实现apology
public class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
return super.invoke(mi);
}
public void saySorry(String name) {
System.out.println("say sorry"+name);
}
}
这个时候我们再来看看他的配置:
<bean id="greetingProxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
ç
<property name="interfaces" value="org.thinkingWalker.proxy.springproxy.yinru.Apology"/>
<property name="target" ref="greetingImpl"/>
<property name="interceptorNames" value="greetingIntroAdvice"/>
bean>
跟织入增强配置的区别是,这里的interfaces代表的意义是动态实现的接口。看看我们的调用方式:
@Test
public void injectTestDynamicIntro(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
GreetingImpl greeting= (GreetingImpl) ac.getBean("greetingProxy2");
greeting.sayHello();
Apology apology= (Apology) greeting;
apology.saySorry("hafadf");
}
可以看到代理对象可以被强转成aplogy对象,所以是可以调用apology对象的方法。
关于增强的方式其实还有一种没有写出来,就是异常增强,就是对异常的捕获,这个大家可以自己看一下。
我们上面可以回顾下,上面讲的增强方式已经可以解决对方法或者是类的横切,但是感觉他切的有点生硬,一刀下去全切掉了。比如说,我能不能对某个类的某些方法做一些增强?或者说对某个包下的某些类做些增强?那这里我们就要接触到我们的切面和切点的概念,切面其实是一个整体的概念,他包括我们上面说的增强,以及接下来要说的切点。这两个一起组成了切面,把切面配置到ProxyFactory中,就生成了能实现我们预期的代理类。
现在对Greeting增加两个方法,一个goodmorning,一个goodafternoon,现在greeting和greetingimpl的代码如下:
public interface Greeting {
void sayHello();
void goodMorning();
void goodAfternoon();
}
public class GreetingImpl implements Greeting {
public void sayHello() {
System.out.println("hello");
}
public void goodMorning() {
System.out.println("morning");
}
public void goodAfternoon() {
System.out.println("afternoon");
}
}
那现在我们来配置一个切面,达到的目的是我只想对greetingimpl中以good开头的方法做增强。我们来一起看看配置,首先需要配置一个符合我们要求的切面:
<bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="greetingAfterAdvice"/>
<property name="pattern"value="org.thinkingWalker.proxy.Greeting.good.*"/>
bean>
//代理类 把切点配置为interceptorNames
<bean id="greetingPointCutProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" value="greetingImpl"/>
<property name="interceptorNames" value="greetingAdvisor"/>
<property name="proxyTargetClass" value="true"/>
bean>
可以看到我们匹配的是在greeting类中以good开头的方法,这样他就只会对goods开头的方法增强。
这个关于切面的配置还是挺多的,所以项目里会采用Aspect来做aop,我们一起来看看aspect.
Aspect是一个开源的用来做aop的插件,我们来看用了aspect做增强有多简单。先看一个例子:
@Aspect
@Component
public class GreetingAspect {
/**
* 第一个*表示返回值任意 最后一个*表示切点的方法,后面的两个。。表示参数随意
* @param pjp
* @return
* @throws Throwable
*/
@Around("execution(* org.thinkingWalker.proxy.GreetingImpl.good*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
before();
Object result = pjp.proceed();
after();
return result;
}
private void after() {
System.out.println("after");
}
private void before() {
System.out.println("before");
}
}
上面就是一个环绕增强
@Aspect表示他是一个advisor(增强)
@around表示他是环绕增强,里面的参数表示他的切点。非常简单直观。
然后需要在配置文件里配置:
<aop:aspectj-autoproxy proxy-target-class="true"/>
这样配置后就可以增强方法了。
aspect还可以基于注解去做切点我们一起看下:
先定一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
}
看看切面类怎么写:
@Aspect
@Component
public class GreetingAspect2 {
/**
* 把Tag注解做为切点
* @param pjp
* @return
* @throws Throwable
*/
@Around("@annotation(org.thinkingWalker.proxy.springaspect.Tag)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
before();
Object result = pjp.proceed();
after();
return result;
}
private void after() {
System.out.println("after");
}
private void before() {
System.out.println("before");
}
}
是不是写起来很爽,比传统那个简单很多呀。
再来看下引入增强
@Aspect
@Component
public class GreetingAspect3 {
@DeclareParents(value = "org.thinkingWalker.proxy.GreetingImpl", defaultImpl = ApologyImpl.class)
private Apology apology;
}
value表示要增强的类,defaultimpl表示引入接口的默认实现类。所以我们需要对引入的类写一个实现类。测试类和上面的一样,这里就不重复写了。
Aop我暂时能写的也就这么多,上面写的主要参考吴勇的《从零开始写javaweb框架》,其实我写的这些只是停留在用的阶段。而他的书主要的思想是先学会怎么用,然后自己写一个这样的轻量的框架,所以书上有介绍关于如何自己写aop框架,水平和精力有限,暂时自己还达不到完全消化完然后分享给大家,所以这个留着自己以后钻研吧。
水平有限,如果有错误的地方,请大家包含。
喝了一杯苦茶,听着五月天的倔强,还能再奋斗一个小时。希望各位小伙伴都能在该奋斗的年纪好好奋斗,学习态度真的很重要,我就是放松了自己将近一年,把自己最引以为傲的学习态度都快丢了。每天学习,有节制的玩,这样既能保持自己的学习态度又可以经历量变到质变,加油!!!