加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

【JavaWeb-24】AOP介绍和术语、手动/半自动/自动实现AOP、基于XM

发布时间:2020-12-16 05:39:58 所属栏目:百科 来源:网络整理
导读:1、什么是AOP?面向切面编程。它和面向对象编程(OOP)都是一种编程思想。AOP也是为了实现代码重用,只是它的代码重用是通过代理来实现的,而OOP的代码重用是通过集成来实现的。比如我有一个B类,B类里面有几个方法,现在的需求是不破坏B类的前提下,给B类里

1、什么是AOP?面向切面编程。它和面向对象编程(OOP)都是一种编程思想。AOP也是为了实现代码重用,只是它的代码重用是通过代理来实现的,而OOP的代码重用是通过集成来实现的。比如我有一个B类,B类里面有几个方法,现在的需求是不破坏B类的前提下,给B类里的几个方法都添加事务。

——如果采用OOP的思想,就是我们继承B类,在新的类里面重写这几个方法,有几个方法,就如下德重写几遍。

class C extends B{
    public void aaa(){
        //开启事务
        super.aaa();
        //提交事务
    }
}

——AOP思想的话,我们需要一个新的类,里面封装的是我们要给B类增强的功能代码,比如此处的事务管理代码。然后我们写一个B类的代理类,我们在代理类里面对B类的每个方法执行前后进行改造,执行前加上开启事务,执行结束加上提交事务。

——这个代理类就是我们学习AOP需要掌握的东西,我们先手写代理类,实际中代理类是由spring帮我们自动生成的。

——动态代理类的实现方法有两种:一种是接口+实现类的方式,spring采用的是jdk的动态代理Proxy。另一种是实现类方式,spring采用的是cglib字节码增强。

2、AOP术语。

——target目标类就是需要被代理被增强的类。joinpoint连接点就是可能被增强的方法。pointcut切入点就是被增强的方法。advice通知/增强,也就是增强代码,有前置的后置的等等。weaving织入过程就是把advice和pointcut连接起来的过程。proxy代理类就是我们主要研究和书写的东西。最后一个aspect切面,就是advice和pointcut的结合。

——补充一下:切面,一般就是指的切面方法或者切面代码,也就是要切入到业务逻辑里的那些增强功能。为什么叫切面呢?我想大概是因为,如果把业务逻辑想象成从上到下的流程的话(想象成圆柱形的),这些代码就是在这些流程的某些位置切入进去,比如在执行一个方法之前开启事务,在方法之后提交事务等,这些切入就像是在圆柱形的流程的某些地方横切了一刀,所以形象地称为切面编程。其本质是,在运行时动态地将代码(增强代码advice)切入到指定类的指定方法中。一般应用比如事务管理、日志管理、性能监测、缓存等。

3、我们先来看看利用JDK的动态代理实现AOP切面编程的功能。

——先编写一个UserService接口(必须要接口,因为动态代理就是凭借获得这套接口,才说明这个代理最终是代理谁的),写两简单的方法,然后写一个UserServiceImpl实现类。

——再编写一个增强类,也就是切面类,也就是我们需要添加的代码,这个类叫MyAspect,里面写一个myBefore和一个myAfter方法。

——再写一个工厂类,为什么?因为我们是模拟动态代理的过程,实际中spring的代理都是由工厂生成出来的,所以我们也要写一个工厂类MyBeanFactory,里面写一个生成代理类的方法createUserService。下面就是模拟的核心。其实本质上就是生成一个代理类(代理的是UserService),然后在处理方法中添加了切面方法。

package com.hello.factory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.hello.aspect.MyAspect;
import com.hello.service.UserService;
import com.hello.service.impl.UserServiceImpl;


public class MyBeanFactory {
    public static UserService createUserService(){
        //目标类
        final UserService userService=new UserServiceImpl();
        //切面类
        final MyAspect myAspect=new MyAspect();
        //整合,就是生成代理,返回这个代理对象
        UserService userServiceProxy=(UserService) Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(),userService.getClass().getInterfaces(),new InvocationHandler() {

            public Object invoke(Object proxy,Method method,Object[] args)
                    throws Throwable {
                myAspect.myBefore();
                Object invoke = method.invoke(userService,args);
                myAspect.myAfter();
                return invoke;
            }
        });

        return userServiceProxy;
    }
}

——当然,最终使用的就是我们的代理类,而不使用我们的原先的类:

@Test
    public void test01(){
        UserService createUserService = MyBeanFactory.createUserService();
        createUserService.addUser();
        createUserService.updateUser();
    }

源代码:JavaEE JDK动态代理实现AOP切面功能

4、CGLIB字节码增强实现AOP编程。

——字节码增强有很多方式,CGLIB只是其中一种,它增强的核心原理是生成一个目标类的子类,然后对子类进行一些增强,最终返回。所以它不需要目标类的接口,只需要目标类即可。

——首先,我们需要导入jar包,这个jar包分为cglib核心包,和asm依赖包。这两个包我们在Hibernate和Struts里面都看到过,但我们的spring-core核心包里面已经包含了这两个,所以我们直接导入spring的core包即可。

——我们写一个目标类UserServiceImpl,写了一个切面类MyAspect同上,只是在工厂类里面有所不同,不是采用的动态代理,而是字节码增强的方式,但我们可以看到,其实核心逻辑都类似,里面的方法都可以和动态代理的一些方法一一对应起来联系:

package com.hello.factory;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import com.hello.aspect.MyAspect;
import com.hello.service.impl.UserServiceImpl;


public class MyBeanFactory {
    public static UserServiceImpl createUserService(){
        //目标类
        final UserServiceImpl userService=new UserServiceImpl();
        //切面类
        final MyAspect myAspect=new MyAspect();
        //整合,就是生成代理,返回这个代理对象
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(userService.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            ////前3个参数和动态代理的类似,第4个参数其实也有作用,是另一种调用父类方法的方法,我们这里的代理其实是UserServiceImpl的子类
            public Object intercept(Object arg0,Method arg1,Object[] arg2,MethodProxy arg3) throws Throwable {
                myAspect.myBefore();
                Object invoke = arg1.invoke(userService,arg2);
//              Object invokeSuper = arg3.invokeSuper(arg0,arg2);
                myAspect.myAfter();
                return invoke;
//              return invokeSuper;
            }
        });
        UserServiceImpl create = (UserServiceImpl) enhancer.create();
        return create;
    }
}

源代码:JavaEE CGLIB字节码增强方式实现AOP编程

5、知识点。

——利用JDK动态代理实现的方法,我们返回的UserService就是一个动态代理类,对象名称如下:

用CGLIB方式生成的类,从名字也看得出来。

——AOP联盟为Advice做了一些分类。我们只需要掌握环绕通知即可。Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:

  • 前置通知 org.springframework.aop.MethodBeforeAdvice,在目标方法执行前实施增强
  • 后置通知 org.springframework.aop.AfterReturningAdvice,在目标方法执行后实施增强
  • 环绕通知 org.aopalliance.intercept.MethodInterceptor,在目标方法执行前后实施增强
  • 异常抛出通知 org.springframework.aop.ThrowsAdvice,在方法抛出异常后实施增强
  • 引介通知 org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性
环绕通知,必须手动执行目标方法
try{
   //前置通知
   //执行目标方法
   //后置通知
} catch(){
   //抛出异常通知
}

6、spring编写代理:半自动。

——让spring帮我们创建代理对象,我们从spring容器中获取代理对象。所以,我们需要的就是一个目标类(和接口)和切面类,然后配置一下,但是这个切面类我们是要遵循规范的,也就是实现环绕方法并实现了里面的方法,在这个方法里面我们写之前我们写的那些前置和后置代码。然后剩下的生成代理类都是通过spring的xml配置文件配置出来的。

——目标类和它的接口没什么好说的,我们再写一个切面类,这时候有所不同了:

package com.hello.aspect;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor {

    public Object invoke(MethodInvocation arg0) throws Throwable {
        System.out.println("方法前");
        //手动执行方法
        Object proceed = arg0.proceed();
        System.out.println("方法后");
        return proceed;
    }
}

——然后再配置文件:

<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 目标类 -->
    <bean id="userService" class="com.hello.service.UserServiceImpl"></bean>
    <!-- 切面类 -->
    <bean id="myAspect" class="com.hello.aspect.MyAspect"></bean>
    <!-- 代理类,以后真正要用的,就是这个代理类 -->
    <!-- 我们之前说过 FactoryBean只生产一种特定的类,所以ProxyFactoryBean只生产代理类,这是spring自带的工厂 -->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 目标类,值是引用 -->
        <property name="target" ref="userService"></property>
        <!-- 实现的接口,这应该是一个array里面包含一个value,但是只有1个value的时候可以省略array而直接写成如下 -->
        <property name="interfaces" value="com.hello.service.UserService"></property>
        <!-- 切面类,这里面也应该是一个array,里面包含多个value,此处同样省略,但是里面的值可以直接用id,上面的接口没有id,所以只能写全限定名称 -->
        <property name="interceptorNames" value="myAspect"></property>
        <!-- 是否采用cglib,配置了,它一定是采用cglib;不配置时,如果有接口优先采用jdk动态代理,否则用cglib -->
        <property name="optimize" value="true"></property>
    </bean>
</beans>

——最后测试一下:

package com.hello.test;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.hello.service.UserService;

public class TestSemiAutoAOP {
    @Test
    public void test01(){
        String xmlPath="com/hello/aspect/applicationContext.xml";
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserService us=(UserService) classPathXmlApplicationContext.getBean("userServiceProxy");
        us.addUser();
        us.updateUser();
    }
}

源代码:JavaEE spring半自动实现AOP代理

7、上面半自动的是spring帮我们创建代理对象,我们再从spring获得代理对象。也就是说我们在xml配置文件中写一个代理对象的bean。然后使用获取的时候就取这个代理对象bean的id。

而全自动的配置思路和这个不太一样,全自动的配置意思就是,我们还是在xml里面配置正常的目标类和切面类的bean,然后不配置代理对象的bean了,而是做一个aop的配置,这里面的大概意思就是把什么切面方法和什么目标方法连接起来。我们使用的时候,还是获取目标类bean的id,但是因为我们有了aop的配置,所以我们表面上是获取目标类bean的id,但是实际上spring给我们返回的是目标类bean的代理对象。如果我们不需要代理对象的话,只要把那段aop的配置删除即可。

——我们首先需要导入一个jar包,com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

——并且在配置文件里添加一个aop命名空间,这样我们就能使用<aop:xxx>了,这个和之前说的P命名空间类似。仍然是之前的那个文件里面去找。

——然后开始配置。

<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 目标类 -->
    <bean id="userService" class="com.hello.service.UserServiceImpl"></bean>
    <!-- 切面类 -->
    <bean id="myAspect" class="com.hello.aspect.MyAspect"></bean>
    <!-- aop配置,proxy-target-class取true表示采用cglib,否是是jdk动态代理-->
    <aop:config proxy-target-class="true">
        <!-- AspectJ表达式:选择方法(任意返回值 包名.任意类。任意方法(任意参数)) -->
        <aop:pointcut expression="execution(* com.hello.service.UserServiceImpl.*(..))" id="myPointCut"/>
        <!-- advisor表示切面,是由一个切入点和一个切面类组成的切面 -->
        <aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
    </aop:config>
</beans>

——最后测试,注意我们测试的时候,获取对象,是直接获取的目标类,因为我们配置了aop,spring就会自动帮我们返回目标类的代理对象给我们。

@Test
    public void test01(){
        String xmlPath="com/hello/aspect/applicationContext.xml";
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserService us=(UserService) classPathXmlApplicationContext.getBean("userService");
        us.addUser();
        us.updateUser();
    }

源代码:JavaEE spring自动实现AOP代理

8、AspectJ。是基于java的AOP框架,不太常用,它主要用于自定义的开发的时候。我们先说说切入点表达式。

——execution(),用于描述方法。语法execution(修饰符 返回值 包.类.方法名(参数) throws异常)

  • 修饰符,一般省略。public公共方法,*任意
  • 返回值,不能省略。void返回没有值,String返回值字符串,*任意
  • 包,[可省略]。com.itheima.crm固定包,com.itheima.crm.*.service crm包下面子包任意 (例如:com.itheima.crm.staff.service),com.itheima.crm.. crm包下面的所有子包(含自己),com.itheima.crm.*.service.. crm包下面任意子包,固定目录service,service目录任意包
  • 类,[可省略]。UserServiceImpl 指定类,*Impl 以Impl结尾,User* 以User开头,*任意
  • 方法名,不能省略。addUser固定方法,add* 以add开头,*Do 以Do结尾,* 任意
  • (参数)。`()无参,(int) 一个整型,(int,int)两个,(..)参数任意
  • throws,可省略,一般不写。

常用案例:

<aop:pointcut expression="execution(* com.hello.do*.*(..))||execution(* com.hello.*Service.*(..))" id="myPointCut"/>

——除了execution之外,还有:

  • within:匹配包或子包中的方法(了解),within(com.hello.aop..*)
  • this:匹配实现接口的代理对象中的方法(了解),this(com.hello.aop.user.UserDAO)
  • target:匹配实现接口的目标对象中的方法(了解),target(com.hello.aop.user.UserDAO)
  • args:匹配参数格式符合标准的方法(了解),args(int,int)
  • bean(id) 对指定的bean所有的方法(了解),bean(‘userServiceId’)

9、AspectJ的通知类型。注意之前我们说的是AOP的通知类型Advice。这里的通知有6种,需要掌握的就是around环绕通知。

  • before:前置通知(应用:各种校验)。在方法执行前执行,如果通知抛出异常,阻止方法运行。
  • afterReturning:后置通知(应用:常规数据处理)。方法正常返回后执行,如果方法中抛出异常,通知无法执行。必须在方法执行后才执行,所以可以获得方法的返回值。
  • around:环绕通知(应用:十分强大,可以做任何事情)。方法执行前后分别执行,可以阻止方法的执行。必须手动执行目标方法。
  • afterThrowing:抛出异常通知(应用:包装异常信息)。方法抛出异常后执行,如果方法没有抛出异常,无法执行。
  • after:最终通知(应用:清理现场)。方法执行完毕后执行,无论方法中是否出现异常。
环绕

try{
     //前置:before
    //手动执行目标方法
    //后置:afterRetruning
} catch(){
    //抛出异常 afterThrowing
} finally{
    //最终 after
}

我们看源码的话,也是如上面格式一样,如果是after的源码,那么它的主要部分就是写在finally里面的。

10、使用AspectJ。基于XML。先导入jar包,除了4+1之外,还需要aop联盟规范和spring-aop,aspectJ规范和spring-aspects。

——这里基于XML的AspectJ的使用和上面自动实现AOP的编程很像,我们同样需要的就是目标类(和接口)、切面类和xml配置,不同的是此处的切面类不需要实现标准接口和实现标准方法,这里面的方法我们可以随便写跟平常自定义一样,xml里面的配置自然也是有所不同,就这么一些差别。另外,测试几种通知类型的时候,不要一起测,因为它们彼此之间没有什么关联关系,顺序上不容易把握。

——先写目标接口和实现类,我们贴出实现类的代码即可:

package com.hello.service;

public class UserServiceImpl implements UserService {

    public void addUser() {
        System.out.println("addUser方法");
    }

    public Integer updateUser() {
        System.out.println("updateUser方法");
        return 888;
    }

    public void deleteUser() {
        System.out.println("deleteUser方法");
    }
}

下面是切面类的,我们定义一个方法,虽然我们这里名字叫做前置的通知,但说了不算,真正说这个方法是前置通知是通过aop配置指定它为前置通知才行,所以说我们这里的方法名字是任意的随便写。

package com.hello.aspect;

import org.aspectj.lang.JoinPoint;

public class MyAspect {
    //JoinPoint和Joinpoint不要搞混,它是连接点的信息,就是方法的信息
    public void myBefore(JoinPoint joinpoint){
        System.out.println("我是前置通知"+joinpoint.getSignature().getName());
    }
}

然后是配置文件:

<?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 http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 目标类 -->
    <bean id="userService" class="com.hello.service.UserServiceImpl"></bean>
    <!-- 切面类 -->
    <bean id="myAspect" class="com.hello.aspect.MyAspect"></bean>

    <!-- aop配置 -->
    <aop:config>
        <aop:aspect ref="myAspect">
            <!-- 定义了一个切入点 -->
            <aop:pointcut expression="execution(* com.hello.service.UserServiceImpl.*(..))" id="myPointCut"/>
            <!-- 下面这里还有一个pointcut="",但是我们上面配置过切入点了,所以用上面的id,否则可以用这个属性,值是上面的表达式,二选一 -->
            <!-- 这就是定义了一个前置通知,把我们写的方法关联进来 -->
            <!-- 这里method是切面类里面我们自己写的方法名,因为上面已经引用了切面类,所以这里直接写方法名即可 -->
            <aop:before method="myBefore" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试:

@Test
    public void test01(){
        String xmlPath="com/hello/service/applicationContext.xml";
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserService us=(UserService) classPathXmlApplicationContext.getBean("userService");
        us.addUser();
        us.updateUser();
        us.deleteUser();
    }

结果:

我是前置通知addUser
addUser方法
我是前置通知updateUser
updateUser方法
我是前置通知deleteUser
deleteUser方法

——其他几个后置通知、环绕通知、抛出异常通知、after通知等流程都一样,主要是在通知的写法和xml配置参数上稍有差别。我们先说后置通知,这里需要注意的就是返回值的设置和配置:
后置通知:

public void myReturning(JoinPoint joinpoint,Object ret){
    <!-- 把返回值也输出来 -->
    System.out.println("我是后置通知"+joinpoint.getSignature().getName()+"->"+ret);
}

配置:

<!-- 后置通知 -->
<!-- returning取值就是后置通知的第二个参数 -->
<aop:after-returning method="myReturning" pointcut-ref="myPointCut" returning="ret"/>

测试都一样,免了,直接看结果,有返回值的就显示出来了:

addUser方法
我是后置通知addUser->null
updateUser方法
我是后置通知updateUser->888
deleteUser方法
我是后置通知deleteUser->null

——环绕通知。有返回值Object,需要使用JoinPoint的子接口ProceedingJoinPoint 。需要手动执行方法。

public Object myAround(ProceedingJoinPoint joinpoint) throws Throwable{
    System.out.println("我是around的前置");
    Object proceed = joinpoint.proceed();
    System.out.println("我是around的后置");
    return proceed;
}

配置,没什么好说的:

<!-- 环绕通知 -->
            <aop:around method="myAround" pointcut-ref="myPointCut"/>

直接看结果:

我是around的前置
addUser方法
我是around的后置
我是around的前置
updateUser方法
我是around的后置
我是around的前置
deleteUser方法
我是around的后置

——再看抛出异常通知,注意第二个参数是异常信息。

public void myAfterThrowing(JoinPoint joinpoint,Throwable e){
    System.out.println("我是抛出异常通知"+"->"+e.getMessage());
}

看配置:

<!-- 抛出异常通知 -->
<!-- throwing就是抛出异常通知的第二个参数,这个配合和返回值的那个配置类似 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>

测试之前我们把目标类的一个方法修改一下得出异常:

public Integer updateUser() {
    System.out.println("updateUser方法");
    int i=1/0;
    return 888;
}

看结果:

addUser方法
updateUser方法
我是抛出异常通知->/ by zero

——最后一个是最终通知,没什么新增的属性和配置。

public void myAfter(JoinPoint joinpoint){
    System.out.println("我是最终通知"+joinpoint.getSignature().getName());
}

配置:

<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>

结果:

addUser方法
我是最终通知addUser
updateUser方法
我是最终通知updateUser
deleteUser方法
我是最终通知deleteUser

源代码:JavaEE AspectJ基于XML的配置

11、基于注解的AspectJ。主要就是把在xml里面的配置都替换成@xxx之类的。

——我们需要先添加命名空间,context命名空间,因为需要扫描注解类。

<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 扫描注解包 -->
    <context:component-scan base-package="com.hello"></context:component-scan>

    <!-- aop的注解也需要开启 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

然后在UserServiceImpl实现类上面添加:

@Service("userService")

然后在切面类上面添加:

//它三层都不是,所以就用@Component,然后把它声明为成切面,相当于<aop:aspect ref="myAspect">
@Component
@Aspect

在切面类里面声明前置方法:

//此处省略了value=
@Before("execution(* com.hello.service.UserServiceImpl.*(..))")
public void myBefore(JoinPoint joinpoint){
    System.out.println("我是前置通知"+joinpoint.getSignature().getName());
}

就OK了。

——下面流程是一样的,后置通知,注意返回值的写法,以及我们在此声明了一个公共切入点,后面直接引用即可,不用每次都写一大串的表达式了。

//公共切入点声明 private void xxx(){} 引用的时候用方法名
@Pointcut("execution(* com.hello.service.UserServiceImpl.*(..))")
    private void myPointCut(){
}

然后写后置方法即可:

@AfterReturning(value="myPointCut()",returning="ret")
public void myReturning(JoinPoint joinpoint,Object ret){
    System.out.println("我是后置通知"+joinpoint.getSignature().getName()+"->"+ret);
}

——环绕通知。

@Around(value="myPointCut()")
public Object myAround(ProceedingJoinPoint joinpoint) throws Throwable{
    System.out.println("我是around的前置");
    Object proceed = joinpoint.proceed();
    System.out.println("我是around的后置");
    return proceed;
}

——抛出异常通知。

@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinpoint,Throwable e){
    System.out.println("我是抛出异常通知"+"->"+e.getMessage());
}

——最终通知。

@After(value="myPointCut()")
public void myAfter(JoinPoint joinpoint){
    System.out.println("我是最终通知"+joinpoint.getSignature().getName());
}

会使用XML配置后,注解的使用原理是一致的,只需要注意注解的各种写法。

源代码:JavaEE AspectJ基于注解的配置

12、 JdbcTemplate,spring提供的操作JDBC的工具类,类似于DBUtils。它依赖数据源(连接池)dataSource。

——我们先来一个最原始的,使用不适用spring配置文件直接使用api的时候。先创建数据表和导入jar包:

然后写实体类User。此处略去。

最后写一个测试类。直接在测试类里面写数据源的设置,而不是用xml配置。我们在此可知的是:JdbcTemplate模板创建的话需要数据源,然后创建后得到的JdbcTemplate对象可以使用query和update等操作数据

@Test
    public void test01(){
        //创建数据源
        BasicDataSource dataSource=new BasicDataSource();
        //设置数据源
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring01");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        //创建JdbcTemplate模板
        JdbcTemplate jdbcTempplate=new JdbcTemplate(dataSource);

        //操作数据
        jdbcTempplate.update("insert into t_user(username,password) values(?,?)","Eric","1234");
    }

——DBCP配置。我们知道上面只要写到new的都可以用Ioc在xml里面让spring管理,而且写到setxxx的基本都可以在xml里用property注入来解决。所以我们可以管理上面的代码。
我们先写一个UserDao接口和实现类,把操作数据库的代码放到这里面,当然这里面也是用到JdbcTemplate的,作为属性注入。

public class UserDaoImpl implements UserDao {
    private JdbcTemplate jdbcTemplate;
    //我们只要在applicationContext里面配置了UserDaoImpl的bean和这个属性,JdbcTemplate就会自动注入了
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void saveUser(User user) {
        jdbcTemplate.update("update t_user set username=?,password=? where id=?",user.getUsername(),user.getPassword(),user.getId());
    }
}

然后,我们直接写配置了,差不多把上面写的代码都在这里面配置了一遍,从数据源到Jdbc模板,然后到实现类的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 id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring01"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 创建jdbcTemplate,需要数据源,所以在上面创建数据源 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 我们最终要使用的,需要一个jdbcTemplate的ref,所以在上面创建一个jdbcTemplate -->
    <bean id="userDao" class="com.hello.dao.impl.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
</beans>

最终使用:

@Test
    public void test02(){
        String xmlPath="com/hello/domain/applicationContext.xml";
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserDao userDao=(UserDao)classPathXmlApplicationContext.getBean("userDao");

        User u=new User();
        u.setId(1);
        u.setUsername("Andy");
        u.setPassword("9999");

        userDao.saveUser(u);
    }

——我们再来做一遍C3P0的配置,其他不变,主要在配置上面做一些改变,因为有些属性名称和DBCP不一样。所以只改变数据源配置这一块即可。

<!-- C3P0配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring01"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

——我们上面xml配置文件里面创建的JdbcTemplate都是自动创建的,也就是说我们给了一个数据源,然后系统就帮我们自动创建了,那么我们其实是可以省略手动配置JdbcTemplate这个步骤的,相当于我们直接给我们的目标类bean一个数据源,它会帮我们自动创建JdbcTemplate。

这里就需要添加一些规范,比如我们的数据类需要继承自JdbcDaoSupport,这个父类里面会帮我们自动创建JdbcTemplate。

我们在UserDaoImpl 实现类里面如下写,继承了JdbcDaoSupport ,这样我们就不能设置setJdbcTemplate了,因为父类里面有了,而且是final修饰的不允许覆写。

public class UserDaoImpl extends JdbcDaoSupport implements UserDao {

    public void saveUser(User user) {
        this.getJdbcTemplate().update("update t_user set username=?,user.getId());
    }
}

配置文件也变得简单了,配置了一下数据源,然后直接用数据源给目标类的bean当做属性注入即可(这个属性甚至不需要写在我们的目标实现类里面,因为在父类JdbcDaoSupport里面已经写了):

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring01"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <bean id="userDao" class="com.hello.dao.impl.UserDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

——最后,我们还可以使用properties文件来储存数据源的配置数据,然后进一步简化applicationContext里面的东西。

先写properties文件。

然后,修改配置文件,当然得先添加context的命名空间,之前说过。

<!-- 需要先加载属性文件 -->
    <!-- classpath:前缀表示 src下,配置好之后通过 ${key}来获得值 -->
    <context:property-placeholder location="classpath:dataSourceInfo.properties" />

    <!-- 利用属性文件来进行C3P0配置 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.pwd}"></property>
    </bean>

源代码:JavaEE JdbcTemplate的简单示例

其他的bean的书写都不变,这样也可以成功使用JdbcTemplate模板来操作数据库。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读