1.spring整合junit
1.1测试类中的问题和解决思路
jdk自带的junit无法识别spring框架,因此当使用spring整合junit后,导致了异常。依靠spring框架提供的junit,替换掉jdk自带的运行器。
1.2配置步骤
1.2.1导入junit的坐标,junit版本4.12以上
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.0.2.RELEASEversion>
dependency>
1.2.2使用@RunWith注解替换原有运行器,作用于单元测试类类名称上
@RunWith(SpringJUnit4ClassRunner.class)
1.2.3使用@ContextConfiguration 指定 Spring 配置文件的位置
这个注解可以代替ApplicationContext ac = new AnnotationConfigApplicationContext(a.class);作用在类上,只要注解配置类是对的,即便main方法中没有任何方法体也能加载注解配置类
@ContextConfiguration(classes=SpringConfiguration.class) //表示使用注解配置
@ContextConfiguration(locations="classpath:applicationContext.xml") //表示使用xml配置
locations 属性: 用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
classes 属性: 用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
替换掉jdk自带的junit运行器后,applicationContext context = new ....这行代码将不再被支持,也就是说@RunWith必须搭配@ContextConfiguration注解使用
2.基于同一个线程下的Connection控制事物
2.1ThreadLocal对象
ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
threadLocal.set(connection); //底层会创建一个ThreadLocalMap,key:ThreadLocal,value:Connection
threadLocal.get(); //底层会将ThreadLocal作为键,去map集合中查找Connection
threadLocal.remove(); //将ThreadLocal,connection解绑,通常释放连接时,调用此方法
2.2QueryRunner对象
2.2.1DML
int runner.update(sql,args...);
eg: runner.update("insert into account values(id,name)",id,name);
2.2.2DQL
<T> T runner.query(sql,T,args...);
List<Account> T = new BeanListHandler<Account>(Account.class)
Account T = new BeanHandler<Account>(Account.class)
2.2.3AccountDaoImpl连接不再是直接注入QueryRunner,而是通过ConnectionUtils获取,从而实现一组事物共用一个连接
QueryRunner runner = new QueryRunner(DataSource ds);
runner.query(sql,args...);
QueryRunner runner = new QueryRunner();
runner.query(connection,sql,args...);
3.动态代理
3.1基于接口的动态代理
IProduct proxyProduct = (IProduct)Proxy.newProcyInstance(Classloader classLoader,Class[] interface,new InvocationHandler(){ ... });
ClassLoader:目标对象的类加载器
Class[]:目标对象实现接口的字节码数组
InvocationHandler: 具体的增强的代码
3.2基于子类的动态代理
Product proxyProduct = (Product)Enhancer.create(Product.class,new MethodInterceptor(){ ... });
Class:目标对象的字节码
MethodInterceptor: 具体的增强的代码
3.3接口动态代理和子类动态代理
1.前者需要接口,后者不需要接口
2.前者,代理对象与被代理对象是兄弟关系
cglib动态代理,代理对象与被代理对象是父子关系,代理对象继承了被代理对象。
3.注意在使用method.invoke(obj,args)时,绝对不要把obj对象指定为代理对象,这样就是递归
4.AOP
4.1 AOP:全称是 Aspect Oriented Programming 即: 面向切面编程。
把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
4.2 AOP 的实现方式
使用动态代理技术,在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
4.3 AOP中的概念
4.3.1核心概念:
目标类(target): 要被增强的类,这个类必须被spring容器管理
代理类(proxy): 使用动态代理产生目标类的代理
切入点(pointcut):目标类中需要增强的方法,这些方法都称为切入点
通知(advice): 增强类中定义的方法,这些方法用于增强目标方法
切面(aspect): 切入点+通知
4.3.2其他概念:
连接点(joinpoint):目标类中的所有方法,连接点包含切入点
织入(weaving): 将通知方法加到目标方法中的过程,spring aop整个过程就是织入
引入(introduction): 在目标类引入新的属性或者新的方法
4.4基于XML的AOP配置
......
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.baidu.utils.Loggle.*(..))"> aop:pointcut>
<aop:aspect id="loggleAdvice" ref="loggle">
<aop:before method="beforeLoggle" pointcut="execution(* com.baidu.utils.Loggle.*(..))"> aop:before>
<aop:after-returning method="afterReturningLoggle" pointcut-ref="pc" >
aop:after-returning>
<aop:after-throwing ... >aop:after-throwing>
<aop:after ... >aop:after>
aop:aspect>
aop:config>
4.5注意事项:
4.5.1 目标类(切入点方法对应的类)和通知类都需要被spring容器管理;
4.5.2 aop:aspect > id : 切入面的唯一标识; aop:aspect > ref : 指定通知类的Bean对象的id;
4.5.3 aop:before > method : 指定具体的前置通知增强的方法名;
aop:before > pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强;
实验:在目标类的构造方法中调用本类的方法,同时在测试代码中从spring容器获取目标类bean对象,然后通过bean对象调用刚刚的方法,结果先走构造方法中的调用方法,不能触发通知的方法;再走bean对象的调用方法,可以完成通知;验证了目标类和通知类都必须被spring容器管理
4.6切入点表达式的写法:
访问修饰符 返回值类型 全限定类名.方法名(参数列表)
4.6.1标准的表达式写法:
public void com.baidu.service.impl.AccountServiceImpl.saveAccount()
4.6.2访问修饰符可以省略
void com.baidu.service.impl.AccountServiceImpl.saveAccount()
4.6.3返回值可以使用通配符,表示任意返回值
* com.baidu.service.impl.AccountServiceImpl.saveAccount()
4.6.4包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount())
4.6.5包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
4.6.6类名和方法名都可以使用*来实现通配
* *..*.*()
4.6.7参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
4.6.8全通配符写法: * *..*.*(..)
4.6.8实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.baidu.service.impl.*.*(..)
总结:可以用通配符表示任意一个包或者类或者方法,用..表示当前包及其子包,用..表示方法参数可有可无且任意类型
4.7环绕通知的配置:
4.7.1XML中的配置
<aop:aspect id="around" ref="loggle">
<aop:around method="aroundLoggle" pointcut="execution(* com.baidu..*.*(..))">
aop:aspect>
4.7.2环绕通知的方法的配置,环绕通知需要手动调用切入点方法
public void aroundLoggle(ProceedingJoinPoint pjp){
try{
//调用前置通知方法
Object[] args = pjp.getArgs();
pjp.proceed(args);
//调用后置通知方法
}catvh(Throw t){
//调用异常通知方法
t.printStackTrace()
}finally{
//调用最终通知方法
}
}
4.7.3注意事项:
当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
4.8各种通知总结
前置通知 before 目标方法被调用之前,就执行该前置通知方法
后置通知 after-returning 目标方法return返回之后,就执行该返回通知方法
异常通知 after-throwing 当目标方法在执行异常的时候,就会执行该异常通知方法
最终通知 after 目标方法被调用完之后,不关心返回结果,就执行该最终通知方法
环绕通知 around 包裹了目标方法,在目标方法之前和在目标方法之后整个过程,经常使用proceedingJoinPoint.proceed(args)来执行目标方法
每次最多出现3种方法
5.基于注解的AOP配置
5.1导入文档声明
<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">
5.2XML中的配置
<context:component-scan base-package="com.baidu">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
5.3常用注解:
5.3.1目标类和通知类需要被spring容器管理,使用@Component注解,ElementType.TYPE
5.3.2在通知类的@Component下再加一个@Aspect注解,表示这是切面类,ElementType.TYPE
5.3.3在通知类的相应通知方法上加注解,ElementType.METHOD
@Before @AfterReturning @AfterThrowing @After @Around value属性值必须指定,指定为调用切入点方法
@pointcut("execution(* com.baidu..*.*(..))")
public void pc(){} //定义一个切入点方法,无论方法体有没有内容,方法体都不执行,即便故意在方法体中制造异常也没有任何效果。
使用通知注解时,需要在value属性值中调用切入点方法,即:@Before("pc()")
注意导包:import org.aspectj.lang.annotation.*;有可能会导入jdk自带的包
5.4spring中的BUG
通知的执行顺序,正确应该是: @Befor-->@AfterReturning/@AfterThrowing-->@After
而spring容器存在一个BUG,使用注解配置AOP,执行顺序为:
@Befor-->@After-->@AfterReturning/@AfterThrowing