AOP概述
什么是AOP?
- AOP—Aspect Oriented Programming 面向切面编程。
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)。
- Spring AOP使用后纯Java实现,不需要专门的编译工程和类加载器,在运行期间通过代理方式向目标类组织增强代码。
- AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,SpringAOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时期提供横向代码的组织。
AOP底层原理:AOP就是代理机制,动态代理:(JDK 中使用) JDK动态代理,对实现了接口的类生成代理。
Spring中的AOP代理:一种是JDK动态代理,另一种就是CGLib代理机制(对类生成代理)。
AOP相关术语:
- Joinpoint(连接点): 所谓的连接点是指那些可以被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。即连接点指的是那些发可以被拦截。
- Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截。
- Advice(通知/增强): 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知,即增强即切面要完成的功能(它是方法级别的增强)。
- Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期间为类动态的添加一些方法或Field(变量)。它是类级别的增强,在原有类上添加一个属性或方法。
- Target(目标对象): 代理的目标对象,即被增强的对象。
- Weaving(植入): 是把增强应用到目标对象来创建新的代理对象的过程,Spring采用动态代理植入,而AspectJ采用编译期植入和类的装载期植入。
- Proxy(代理): 一个类被AOP植入增强后,就产生一个结果代理类。
- Aspect(切面): 是切入点和通知(引介)的结合。允许有多个切点和通知组合。
AOP的底层实现
JDK动态代理
JDK1.3引入动态代理技术,编写动态代理程序(java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler),如图所示:
使用JDK生成动态代理代码示例如下:
UserDao
package spring3.aop.demo1;
/**
* DAO的接口
*
*/
public interface UserDao {
public void add();
public void update();
}
UserDaoImpl
package spring3.aop.demo1;
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("添加用户");
}
@Override
public void update() {
System.out.println("修改用户");
}
}
JDKProxy
package spring3.aop.demo1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK的动态代理机制
*
* @author liuxun
*
*/
public class JDKProxy implements InvocationHandler {
private UserDao userDao;
public JDKProxy(UserDao userDao) {
super();
this.userDao = userDao;
}
public UserDao createProxy() {
UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(), this);
return proxy;
}
@Override
// 调用目标对象的任何一个方法都相当于invoke()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("add".equals(method.getName())) {
// 记录日志
System.out.println("日志记录===================");
Object result = method.invoke(userDao, args);
return result;
}
return method.invoke(userDao, args);
}
}
SpringTest1
package spring3.aop.demo1;
import org.junit.Test;
public class SpringTest1 {
@Test
public void demo1() {
UserDao userDao = new UserDaoImpl();
userDao.add();
userDao.update();
}
@Test
public void demo2() {
// 被代理对象
UserDao userDao = new UserDaoImpl();
// 创建代理对象的时候传入被代理对象
UserDao proxy = new JDKProxy(userDao).createProxy();
proxy.add();
proxy.update();
}
}
运行结果如下:
使用CGLIB生成代理
对于不使用接口的业务类,无法使用JDK动态代理。可以采用cglib技术,cglib采用非常底层的字节码技术,可以为一个类创建子类,解决无接口代理问题。
CGLIB(code Genaration Library)是一个强大的、高质量的、高性能的Code生成类库,它可以在运行期扩展Java类与实现接口。Hibernate支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成,Hibernate生成持久化类的Javaassist。其实CGLIB生成代理机制的实质就是生成了一个真实对象的子类。
如果要使用CGLIB技术生成代理,需要下载CGLIB的jar包,但是在Spring的核心包Core中已经集成了CGLIB,可以不用直接引用CGLIB的包。如下图所示:
具体代码如下所示:
ProductDao
package spring3.aop.demo2;
public class ProductDao {
public void add() {
System.out.println("添加商品...");
}
public void update() {
System.out.println("修改商品...");
}
}
CGLibProxy
package spring3.aop.demo2;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
* 使用CGLib生成代理对象
*
*/
public class CGLibProxy implements MethodInterceptor {
private ProductDao productDao;
public CGLibProxy(ProductDao productDao) {
super();
this.productDao = productDao;
}
public ProductDao createProxy() {
// 使用CGLIB生成代理
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.为其设置父类
enhancer.setSuperclass(productDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.创建代理
return (ProductDao) enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("add".equals(method.getName())) {
System.out.println("日志记录=================");
// Object obj=method.invoke(productDao, args);
Object obj = methodProxy.invokeSuper(proxy, args);// 代理方法中执行代理对象父类中的方法
return obj;
}
return methodProxy.invokeSuper(proxy, args);
}
}
SpringTest2
package spring3.aop.demo2;
import org.junit.Test;
public class SpringTest2 {
@Test
public void demo1() {
ProductDao productDao = new ProductDao();
productDao.add();
productDao.update();
}
@Test
public void demo2() {
ProductDao productDao = new ProductDao();
ProductDao proxy=new CGLibProxy(productDao).createProxy();
proxy.add();
proxy.update();
}
}
运行结果如下所示
关于CGLIB生成代理时实现的intercept拦截方法参数说明如下:
/**
*@param obj CGlib根据指定父类生成的代理对象
*@param method 拦截的方法
*@param args 拦截方法的参数数组
*@param proxy 方法的代理对象,用于执行父类的方法
*@return
*/
public Object intercept(Object obj, Methodmethod, Object[] args,
MethodProxyproxy) throws Throwable {
... ...
}
注意:最新版本的Spring已经将CGLib开发类引入spring-core-3.2.0.RELEASE.jar
代理知识总结
Spring在运行期间,生成动态代理对象,不需要特殊的编译器。
Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向植入。选择方式如下:
- 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
- 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
程序中应当优先对接口创建代理,便于程序解耦合进行维护。
标记为final的方法,不能被代理,因为无法进行覆盖,原因如下:
- JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰。
- CGLib是针对目标类生成子类,因此类或方法不能使用final进行修饰。
Spring只支持方法连接点,不提供属性连接。
Spring中的AOP
Spring传统的AOP(增强类型)
AOP不是由spring定义,是由AOP联盟定义的。
AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
- 前置通知 org.springframework.aop.MethodBeforeAdvice 在目标方法执行前进行增强。
- 后置通知 org.springframework.aop.AfterReturningAdvice 在目标方法执行后实施增强。
- 环绕通知 org.aopalliance.intercept.MethodInterceptor 在目标方法执行前后实施增强。
- 异常抛出通知 org.springframework.aop.ThrowsAdvice 在方法抛出异常后实施增强。
- 引介通知 org.springframework.aop.IntroductionInterceptor 在目标类中增加一些新的方法或属性。
Spring AOP切面类型
- Advisor: Spring中传统切面,Advisor都是由一个切点和一个通知组合,而Aspect是由多个切点和多个通知组合。Advisor代表一般切面,Advice本身就是一个切面,对目标类的所有方法进行拦截(不带有切点的切面,对所有方法进行拦截)
- PointcutAdvisor: 代表具有切点的切面,可以指定拦截目标类的哪些方法。
- IntroductionAdvisor: 代表引介切面,针对引介通知使用切面(在spring中用不到)。
Spring中的AOP开发
Advisor切面案例—前置通知
针对所有方法的增强:不带切点的切面,开发步骤如下:
第一步:引入spring aop相关的jar包
- spring-aop-3.2.0.RELEASE.jar
- com.springsource.org.aopalliance-1.0.0.jar(来自AOP联盟 在依赖包中)
第二步:编写被代理对象(接口和实现类)
第三步:编写增强的代码(编写增强类实现增强类型的接口)
第四步: 生成代理(配置生成代理)
<bean id="helloAdvice" class="cn.test.springaop.HelloServiceBeforeAdvice"></bean>
<bean id="target" class="cn.test.springaop.HelloService" />
<bean id="helloService" class="org.springframework.aop.framework.ProxyFactoryBean" >
<property name="proxyInterfaces" value="cn.test.springaop.IHelloService" />
<!--如果不是针对接口代理,可以设置 <property name="proxyTargetClass" value="true"></property> ,将使用CGLib-->
<property name="interceptorNames" value="helloAdvice"></property>
<property name="target" ref="target"></property>
</bean>
spring生成代理基于ProxyFactoryBean类,底层自动选择使用JDK的动态代理还是CGLIB的代理。
ProxyFactoryBean常用的可配置属性如下:
target: 代理的目标对象
proxyInterfaces: 代理要实现的接口,值是接口的全路径,如果代理实现多个接口可以使用如下方式赋值:
<list>
<value></value>
......
</list>
proxyTargetClass: 是否对类代理而不是接口,设置为true时,使用CGLib代理。
interceptorNames: 需要织入目标的Advice名称。
singleton: 返回代理是否为单实例,默认为单例。
optimize: 当设置为true时,强制使用CGLib。
示例代码如下:
定义接口CustomerDao
package spring3.aop.demo3;
public interface CustomerDao {
public void add();
public void update();
public void delete();
public void find();
}
真实实现类CustomerDaoImpl
package spring3.aop.demo3;
public class CustomerDaoImpl implements CustomerDao {
public void add() {
System.out.println("添加客户");
}
public void update() {
System.out.println("修改客户");
}
public void delete() {
System.out.println("删除客户");
}
public void find() {
System.out.println("查询客户");
}
}
定义增强类型MyBeforeAdvice
package spring3.aop.demo3;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 前置增强
*/
public class MyBeforeAdvice implements MethodBeforeAdvice {
/**
* method:执行的方法
* args:参数
* target:目标对象
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置增强...");
}
}
配置增强applicationContext.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 不带有切点的切面 -->
<!-- 定义目标对象 -->
<bean id="customerDao" class="spring3.aop.demo3.CustomerDaoImpl" />
<!-- 定义增强 -->
<bean id="beforeAdvice" class="spring3.aop.demo3.MyBeforeAdvice" />
<!-- spring支持配置生成代理 -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 设置目标对象 -->
<property name="target" ref="customerDao" />
<!-- 设置实现的接口,value中写接口的全路径 -->
<property name="proxyInterfaces" value="spring3.aop.demo3.CustomerDao" />
<!-- 需要使用value: 需要实现增强代码类的名称 -->
<property name="interceptorNames" value="beforeAdvice" />
<!-- 强制使用CGLIB代理 -->
<property name="optimize" value="true" />
</bean>
</beans>
新建测试类SpringTest3
package spring3.aop.demo3;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest3 {
@Autowired
// @Qualifier("customerDao") // 需要注入真实的对象,必须注入代理对象
@Qualifier("customerDaoProxy")
private CustomerDao customerDao;
@Test
public void demo1() {
customerDao.add();
customerDao.update();
customerDao.delete();
customerDao.find();
}
}
测试demo1 运行结果如下
打断点调试查看CustomerDao的类型
发现虽然实现的是接口却由CGLIB生成的动态代理对象,因为在XML中定义ProxyFactoryBean的属性时指定了optimize属性为true,即强制使用CGLIB生成动态代理对象。如果不设置则由Spring自动选择生成动态代理对象的方式。去掉此属性,断点调试查看如下:
因为是针对接口生成代理对象,所以Spring会优先选择使用JDK生成动态代理对象。
注意:可能由于JDK版本原因,使用JDK1.5时 JDK的动态代理会报错。当使用JDK1.8时,虽然能使用JDK动态代理,但是@RunWith和@ContextConfiguration会抛出参数非法异常,所以最好选择JDK1.7
PointcutAdvisor切点切面案例—环绕通知
对于不带有切点的Advisor,其Advice增强本身就是一个切面 默认对目标类所有方法进行增强所以以上案例中配置切点切面名称的interceptorNames属性就是自己配置的Advice。所谓的带有切点的切面就是针对目标类中指定的某些方法进行增强。正是因为使用普通的Advice作为切面将对目标类的所有方法进行拦截,不够灵活,所以在实际开发中 常采用带有切点的切面。
常用PointcutAdvisor实现类如下:
- DefaultPointcutAdvisor最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面。
- RegexpMethodPointcutAdvisor 构造正则表达式定义切点切面。
示例代码如下所示:
OrderDao (代理目标类)
package spring3.aop.demo4;
/**
* 目标对象
*
*/
public class OrderDao {
public void add() {
System.out.println("添加订单");
}
public void update() {
System.out.println("修改订单");
}
public void delete() {
System.out.println("删除订单");
}
public void find() {
System.out.println("查询订单");
}
}
MyAroundAdvice (Advice通知增强类)
package spring3.aop.demo4;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 增强的类
* 使用的是环绕增强
* 注意:对于增强类型的接口可以使用AOP联盟的也可以使用Spring扩展后自带的
*/
public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前增强...");
Object result=methodInvocation.proceed(); // 指定目标对象的方法
System.out.println("环绕后增强...");
return result;
}
}
applicationContext.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 带有切点的切面 -->
<!-- 定义目标对象 -->
<bean id="orderDao" class="spring3.aop.demo4.OrderDao" />
<!-- 定义增强 -->
<bean id="aroundAdvice" class="spring3.aop.demo4.MyAroundAdvice" />
<!-- 定义切点切面 -->
<bean id="myPointcutAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 定义表达式,规定哪些方法执行拦截 -->
<!-- . 任意字符 * 任意个 .* 任意个任意字符 -->
<!-- 对所有方法进行拦截 -->
<!-- <property name="pattern" value=".*"/> -->
<!-- 拦截指定类中以add开头的所有方法 -->
<!-- <property name="pattern" value="spring3\.aop\.demo4\.OrderDao\.add.*"/> -->
<!-- 拦截方法名包含add的方法 -->
<!-- <property name="pattern" value=".*add.*"/> -->
<!-- 多种格式的拦截:拦截方法名中包含add或find的方法 -->
<property name="patterns" value=".*add.*,.*find.*" />
<!-- 应用增强 -->
<property name="advice" ref="aroundAdvice" />
</bean>
<!-- 定义生成代理对象 -->
<bean id="orderDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置目标 -->
<property name="target" ref="orderDao" />
<!-- 针对类代理 -->
<property name="proxyTargetClass" value="true" />
<!-- 在目标应用上增强 -->
<property name="interceptorNames" value="myPointcutAdvisor" />
</bean>
</beans>
运行结果如下:
自动代理
前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中有非常多的Bean,如果每个都配置ProxyFactoryBean 会造成非常大的开发维护量。
解决方案:自动代理(基于后处理Bean,在Bean创建过程中完成的增强,生成的Bean就是代理)
自动代理有如下几种实现方式:
- BeanAutoProxyCreator 根据Bean名称创建代理。
- DefaultAdvisorAutoProxyCreator 根据Advisor本身包含的信息创建代理。
- AnnotionAwareAspectJAutoProxyCreator 基于Bean中AspectJ注解进行自动代理。
BeanNameAutoProxyCreator: 按照名称生成代理。
示例代码如下:
applicationContext2.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义目标对象 -->
<bean id="customerDao" class="spring3.aop.demo3.CustomerDaoImpl" />
<bean id="orderDao" class="spring3.aop.demo4.OrderDao" />
<!-- 定义增强 -->
<bean id="beforeAdvice" class="spring3.aop.demo3.MyBeforeAdvice" />
<bean id="aroundAdvice" class="spring3.aop.demo4.MyAroundAdvice" />
<!-- 自动代理:按照Bean名称的代理 基于后处理Bean,后处理Bean不需要配置ID -->
<bean
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- 对bean名称以Dao结尾的进行自动代理 -->
<property name="beanNames" value="*Dao" />
<!-- 定义通知类型 也可以指定多个 -->
<property name="interceptorNames" value="beforeAdvice" />
</bean>
</beans>
SpringTest5 新建测试类
package spring3.aop.demo5;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring3.aop.demo3.CustomerDao;
import spring3.aop.demo4.OrderDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringTest5 {
@Autowired
@Qualifier("orderDao")
private OrderDao OrderDao;
@Autowired
@Qualifier("customerDao")
private CustomerDao CustomerDao;
@Test
public void demo1() {
OrderDao.add();
OrderDao.delete();
CustomerDao.update();
}
}
运行结果如下:
DefaultAdvisorAutoProxyCreator:根据切面中定义的信息生成代理
示例代码如下:
applicationContext3.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义目标对象 -->
<bean id="customerDao" class="spring3.aop.demo3.CustomerDaoImpl" />
<bean id="orderDao" class="spring3.aop.demo4.OrderDao" />
<!-- 定义增强 -->
<bean id="beforeAdvice" class="spring3.aop.demo3.MyBeforeAdvice" />
<bean id="aroundAdvice" class="spring3.aop.demo4.MyAroundAdvice" />
<!-- 定义一个带有切点的切面 -->
<bean id="mypointcutAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value=".*add.*" />
<property name="advice" ref="aroundAdvice" />
</bean>
<!-- 根据切面信息自动生成代理 -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
</beans>
SpringTest6 测试类
package spring3.aop.demo6;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring3.aop.demo3.CustomerDao;
import spring3.aop.demo4.OrderDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class SpringTest6 {
@Autowired
@Qualifier("orderDao")
private OrderDao orderDao;
@Autowired
@Qualifier("customerDao")
private CustomerDao customerDao;
@Test
public void demo1() {
orderDao.add();
orderDao.update();
orderDao.delete();
customerDao.add();
}
}
运行结果如下:
注意:区分基于ProxyFactoryBean的代理与自动代理的区别
ProxyFactoryBean必须先有被代理对象,将被代理对象传入代理类中生成代理,而自动代理基于后处理Bean,在Bean的生成过程中,就产生了代理对象,把代理对象返回,生成的Bean已经是代理对象。
Spring中AspectJ的AOP(*****)
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来遵守Java字节编码规范的Class文件。
AspectJ是一个基于Java语言的AOP框架。
Spring2.0以后新增了对AspectJ切点表达式的支持。
@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。
新版本的Spring框架,建议使用AspectJ方式来开发AOP。
使用AspectJ需要导入Spring AOP和AspectJ相关的jar包。
- spring-aop-3.2.0.RELEASE.jar
- com.springsource.org.aopalliance-1.0.0.jar
- spring-aspects-3.2.0.RELEASE.jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
通过配置启用@AspectJ: 即在配置文件中引入AOP的约束(同样搜索xsd-config.xml) 粘贴过来:然后开启AspectJ自动代理<aop:aspectj-autoproxy/>
示例如下:
<?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">
<!-- 开启AspectJ自动代理-->
<aop:aspectj-autoproxy />
</beans>
AspectJ表达式
在通知中通过value属性定义切点
通过execution函数,可以定义切点的方法切入。
语法:execution(<访问修饰符>?<返回类型><方法名>(参数)<异常>)其中?表示可以省略
例如:
- 匹配所有类的所有方法 execution(public * *(..)).. 任意个任意类型参数
- 匹配指定包下所有类所有方法 execution(* aspectj.test.dao.*(..)) 不包含子包
- execution(* aspectj.test.dao..*(..)) ..* 表示当前包以及子孙包下所有类
- 匹配指定类所有方法 execution(* aspectj.test.service.UserService.*(..))
- 匹配实现特定接口的所有类方法 execution(* aspectj.test.dao.GenericDao+.*(..))
- 匹配所有save开头的方法 execution(* save*(..))
AspectJ基于注解的实现
AspectJ的通知类型,@AspectJ提供不同的通知类型
- @Before 前置通知,相当于BeforeAdvice
- @AfterReturning 后置通知,相当于AfterReturningAdvice
- @Around 环绕通知,相当于MethodInterceptor
- @AfterThrowing 抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否有异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor(极少使用)
@Before前置通知,可以在对应的方法中传入JointPoint对象,用来获取切点信息。
@AfterReturning 后置通知 除了可以在方法中传入JointPoint对象之外,还可以通过returning属性来定义方法的返回值作为参数参数类型一般是Object
@Around环绕通知,around方法的返回值就是目标代理方法执行返回值,参数为ProceedingJoinPoint可以调用拦截目标方法执行,特别要注意:如果不调用ProceedingJoinPoint的proceed方法,那么目标方法就被拦截了。
@AfterThrowing 抛出通知 通过设置throwing属性,可以设置发生异常对象参数Throwable
通过@Pointcut为切点命名
- 在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义。
- 切点方法: private void 无参数方法,方法名为切点名。
- 当通知多个切点时,可以使用||进行连接
新建项目首先导入Spring中必须的几个包bean、context、core、expression 日志相关commons.loggoing、log4j 然后导入Spring AOP的包(aop、aop联盟的包(依赖包中)) 最后导入Aspect的包以及依赖包。
新建配置文件,引入AOP约束,配置自动代理,最后编码实现。
示例代码如下:
编写被增强的类 UserDao
package aop.aspectj.demo1;
public class UserDao {
public void add(){
System.out.println("添加用户");
}
public int update(){
System.out.println("修改用户");
return 1;
}
public void delete(){
System.out.println("删除用户");
int d = 1/ 0;
}
public void find(){
System.out.println("查询用户");
}
}
MyAspect 注解实现AspectJ的切面
package aop.aspectj.demo1;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
*
* 切面类:就是切点和增强的组合
*
*/
@Aspect
public class MyAspect {
@Before("execution(* aop.aspectj.demo1.UserDao.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置增强..." + joinPoint);
}
@AfterReturning(value = "execution(* aop.aspectj.demo1.UserDao.update(..))", returning = "returnVal")
public void afterReturning(Object returnVal) {
System.out.println("后置增强...方法的返回值" + returnVal);
}
@AfterThrowing(value = "MyAspect.myPointcut1()", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("警告:出现异常了!!! " + e.getMessage());
}
@After("MyAspect.myPointcut1()||MyAspect.myPointcut2()")
public void after(){
System.out.println("最终通知...");
}
@Pointcut("execution(* aop.aspectj.demo1.UserDao.delete(..))")
public void myPointcut1() {
}
@Pointcut("execution(* aop.aspectj.demo1.UserDao.find(..))")
public void myPointcut2() {
}
}
applicationContext.xml 引入AOP约束,配置自动代理
<?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">
<!-- 自动生成代理 底层就是AnnotationAwareAspectJAutoProxyCreator -->
<aop:aspectj-autoproxy />
<bean id="userDao" class="aop.aspectj.demo1.UserDao" />
<bean id="myAspect" class="aop.aspectj.demo1.MyAspect" />
</beans>
编写测试类 SpringTest1
package aop.aspectj.demo1;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest1 {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
@Test
public void demo1() {
userDao.add();
userDao.update();
userDao.find();
userDao.delete();
}
}
运行结果如下:
AspectJ基于XML的实现
示例代码如下:
ProductDao
package aop.aspectj.demo2;
public class ProductDao {
public int add() {
System.out.println("添加商品...");
int d = 10 / 0;
return 100;
}
public void update() {
System.out.println("修改商品...");
}
public void delete() {
System.out.println("删除商品...");
}
public void find() {
System.out.println("查询商品...");
}
}
MyAspectXML 切面类
package aop.aspectj.demo2;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类
*/
public class MyAspectXML {
public void before() {
System.out.println("前置通知...");
}
public void afterReturning(Object returnVal) {
System.out.println("后置通知...返回值:" + returnVal);
}
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前增强...");
Object result = proceedingJoinPoint.proceed();
System.out.println("环绕后增强...");
return result;
}
public void afterThrowing(Throwable e) {
System.out.println("异常通知..." + e.getMessage());
}
public void after() {
System.out.println("最终通知...");
}
}
applicationContext2.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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义被增强的类 -->
<bean id="productDao" class="aop.aspectj.demo2.ProductDao" />
<!-- 定义切面 -->
<bean id="myAspectXML" class="aop.aspectj.demo2.MyAspectXML" />
<!-- 定义AOP配置 -->
<aop:config>
<!-- 定义切点 -->
<aop:pointcut expression="execution(* aop.aspectj.demo2.ProductDao.add(..))" id="myPointcut"/>
<aop:pointcut expression="execution(* aop.aspectj.demo2.ProductDao.delete(..))" id="myPointcut2"/>
<!-- 配置Aspect增强类 -->
<aop:aspect ref="myAspectXML">
<!-- 前置通知 -->
<aop:before method="before" pointcut="execution(* aop.aspectj.demo2.ProductDao.update(..))" />
<!-- 后置通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut" returning="returnVal"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="myPointcut2"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
SpringTest2 测试类
package aop.aspectj.demo2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringTest2 {
@Autowired
@Qualifier("productDao")
private ProductDao productDao;
@Test
public void demo1() {
productDao.update();
productDao.delete();
productDao.find();
productDao.add();
}
}
运行结果如下
Spring JDBCTemplate
Spring对不同持久化技术的支持
Spring为支持各种持久化技术,都提供了简单操作的模板和回调,如下图所示:
JdbcTemplate的使用
Spring JDBC是Spring提供的持久层技术。
简化JDBC API开发,使用上和Apache公司的DBUtils框架非常类似。
首先要导入必要的jar包到工程目录。
导入Spring核心开发包到创建的工程中:
- spring-beans-3.2.0.RELEASE.jar
- spring-context-3.2.0.RELEASE.jar
- spring-core-3.2.0.RELEASE.jar
- spring-expression-3.2.0.RELEASE.jar
还需要下载日志整合包以及log4j:
- com.springsource.org.apache.commons.logging-1.1.1
- com.springsource.org.apache.log4j-1.2.15
因为要使用JDBCTemplate模板,必须导入JDBC模板开发包:
- spring-jdbc-3.2.0.RELEASE.jar
- spring-tx-3.2.0.RELEASE.jar
因为在本例中要进行测试,需要导入
spring-test-3.2.0.RELEASE.jar
注意:最后一定要导入数据库驱动
简单示例程序如下:
// JDBC模板 依赖连接池获得数据库连接,所有必须先构造连接池
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring");
dataSource.setUsername("root");
dataSource.setPassword("123");
// 创建JDBC模板
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// 建表SQL语句
String sql = "create table customers(id int primary key auto_increment,name varchar(20))";
jdbcTemplate.execute(sql);
配置连接池(spring自带的、dbcp、c3p0)
从以上实例中可以看出,SPringJDBCAD模板的使用,必须依赖DataSource数据库连接池。
在实际开发中,通过Spring配置文件来配置(也可以手动编码实现,但是很少编码实现,因为手动配置的连接池默认就是一个单例对象)
常用的数据源如下:
- Spring自带的数据源实现类 DriverManagerDataSource
- DBCP数据源 BasicDataSource
- C3P0数据源 ComboPooledDataSource
Spring默认的连接池
<!-- 配置Spring默认的连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring3_day02"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</bean>
DBCP连接池
使用DBCP连接池需要导入2个包 一个是dbcp包,另一个是dbcp依赖的pool。如下所示:
- com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar
- com.springsource.org.apache.commons.pool-1.5.3.jar
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring"></property>
<property name="username" value="root"></property>
<property name="password" value="123"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置C3P0连接池
使用C3P0连接池需要导入com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring"></property>
<property name="user" value="root"></property>
<property name="password" value="123"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
将参数配置到属性文件中
在配置文件中需要引入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: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">
<!-- 引入第三方 properties配置 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- ${key} 可以读取properties文件中配置 key对应value -->
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
==================================================
<context:property-placeholder> 可以写为
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"></property>
</bean>
配置连接池代码示例如下:
导入相应jar包,配置log4j.properties 如下:
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
applicationContext.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: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">
<!-- 配置Spring默认连接池 -->
<bean id="dataSource_default"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///spring3_day02" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<!-- 配置DBCP连接池 -->
<!-- <bean id="dataSource_dbcp" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///spring3_day02" />
<property name="username" value="root" />
<property name="password" value="root"></property>
</bean> -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置C3P0连接池 -->
<!-- <bean id="dataSource_c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean> -->
<!-- 定义JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource_default" />
</bean>
</beans>
SpringTest1
package spring3.jdbctemplate.demo1;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest1 {
@Autowired
@Qualifier("jdbcTemplate")
JdbcTemplate jdbcTemplate;
@Test
// 手动配置Spring自带的数据源
public void demo1() {
// 创建连接
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// 设置参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring3_day02");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 使用JDBC模板
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.execute("create table user (id int primary key auto_increment,name varchar(20))");
}
@Test
public void demo2() {
jdbcTemplate.execute("insert into user values(null,'spring_dataSource')");
}
}
运行demo1
运行demo2
编写DAO实现数据的CURD
为了方便DAO中注入JDBCTemplate,Spring为每一个持久化技术都提供了支持类。
使用JDBC模板时方法如下:
编写DAO的时候:Public class UserDao extends JdbcDaoSupport{
}
进行CRUD的操作;
* 保存:update(String sql,Object... args)
* 修改:update(String sql,Object... args)
* 删除:update(String sql,Object... args)
查询:
* 简单查询:
* select count(*) from user; --- queryForInt(String sql);
* select name from user where id = ?; --- queryForObject(String sql,Class clazz,Object... args);
* 复杂查询:(返回对象,和对象集合)
* select * from user where id = ? --- queryForObjectString sql,RowMapper<T> rowMapper,Object... args);
* select * from user; --- query(String sql,RowMapper<T> rowMapper,Object... args);
示例代码如下:
User
package spring3.jdbctemplate.demo2;
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
UserDao
package spring3.jdbctemplate.demo2;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class UserDao extends JdbcDaoSupport {
public void add(User user) {
String sql = "insert into user values(null,?)";
this.getJdbcTemplate().update(sql, user.getName());
}
public void update(User user) {
String sql = "update user set name= ? where id= ?";
this.getJdbcTemplate().update(sql);
}
public void delete(User user) {
String sql = "delete from user where id= ?";
this.getJdbcTemplate().update(sql, user.getId());
}
public int findCount() {
String sql = "select count(*) from user";
return this.getJdbcTemplate().queryForInt(sql);
}
public String findNameById(int id) {
String sql = "select name from user where id = ?";
return this.getJdbcTemplate().queryForObject(sql, String.class, id);
}
public User findById(int id) {
String sql = "select * from user where id = ?";
return this.getJdbcTemplate().queryForObject(sql, new UserRowMapper(), id);
}
public List<User> findAll() {
String sql = "select * from user";
return this.getJdbcTemplate().query(sql, new UserRowMapper());
}
class UserRowMapper implements RowMapper<User> {
/**
* rs: 结果集 rowNum: 行号
*/
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
return user;
}
}
}
在applicationContext.xml文件中添加如下配置
<bean id="userDao" class="spring3.jdbctemplate.demo2.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
测试SpringTest2
package spring3.jdbctemplate.demo2;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest2 {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
@Test
public void demo1() {
User user = new User();
user.setName("小胖");
userDao.add(user);
}
@Test
public void demo2() {
User user = new User();
user.setId(1);
user.setName("小边");
userDao.update(user);
}
@Test
public void demo3() {
User user = new User();
user.setId(1);
userDao.delete(user);
}
@Test
public void demo4() {
int count = userDao.findCount();
System.out.println(count);
}
@Test
public void demo5() {
String name = userDao.findNameById(3);
System.out.println(name);
}
@Test
public void demo6() {
User user = userDao.findById(3);
System.out.println(user);
}
@Test
public void demo7() {
List<User> list = userDao.findAll();
for (User user : list) {
System.out.println(user);
}
}
}
由代码和测试可以发现 JDBCTemplate和DBUtils很类似