1、AOP的原理就是使用动态代理:
首先我们看下为什么要使用动态代理,先看看静态代理有什么问题。
演示一个静态代理的例子:(jdk的代理是基于接口的)
功能就是,保存数据的时候添加事务处理
//定义接口 PersonDao.java
public interface PersonDao {
public void savePerson();
}
//定义个实现类 PersonDaoImpl.java
public class PersonDaoImpl implements PersonDao{
public void savePerson() {
System.out.println("save person");
}
}
//定义一个事务类 Transaction.java
public class Transaction {
public void beginTransaction(){
System.out.println("begin transaction");//开启事务
}
public void commit(){
System.out.println("commit");//提交事务
}
}
//定义个代理类 PersonDaoProxy.java
public class PersonDaoProxy implements PersonDao{ //实现相同的接口
private PersonDao personDao;
private Transaction transaction;
public PersonDaoProxy(PersonDao personDao,Transaction transaction) {
super();
this.personDao = personDao;//通过构造传进来
this.transaction = transaction;
}
public void savePerson() {
this.transaction.beginTransaction();
this.personDao.savePerson();
this.transaction.commit();
}
}
//测试 ProxyTest.java
public class ProxyTest {
@Test
public void testProxy(){
PersonDao personDao = new PersonDaoImpl();
Transaction transaction = new Transaction();
PersonDaoProxy proxy = new PersonDaoProxy(personDao, transaction);
proxy.savePerson();
}
}
看看静态代理有什么问题:
a.静态代理没有事务重用,我们还是要在每个方法里面调用this.transaction.beginTransaction();
b.如果我们dao层有100个方法,我们就需要写100个proxy类,变得更麻烦了。接口中定义了多少个方法,
proxy也要实现多少个方法。
c.如果一个proxy实现了多个接口,如果其中的一个接口发生变化(添加了一个方法),那么proxy也要做相应的改变。
所以静态代理我们维护起来会更加的麻烦。
2、动态代理:
既然静态代理这么多的问题,那么我们看看动态代理。
顾名思义,动态代理就是动态生成Proxy类,不需要我们自己创建Proxy了。
演示下:
public interface PersonDao {
public void savePerson();
public void updatePerson();
}
public class PersonDaoImpl implements PersonDao{
public void savePerson() {
System.out.println("save person");
}
public void updatePerson() {
System.out.println("update person");
}
}
public class Transaction {
public void beginTransaction(){
System.out.println("begin transaction");
}
public void commit(){
System.out.println("commit");
}
}
//最主要的设置拦截器
public class MyInterceptor implements InvocationHandler{
private Object target;//目标类
private Transaction transaction;
public MyInterceptor(Object target, Transaction transaction) {
super();
this.target = target;
this.transaction = transaction;
}
//proxy这个是代表的我们MyInterceptor这个类,千万别写成method.invoke(proxy);
//否则就死循环,jvm就挂了
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if("savePerson".equals(methodName)||"updatePerson".equals(methodName)
||"deletePerson".equals(methodName)){
this.transaction.beginTransaction();//开启事务
method.invoke(target);//调用目标方法
this.transaction.commit();//事务的提交
}else{
method.invoke(target);
}
return null;
}
}
//使用
public class JDKProxyTest {
@Test
public void testJDKProxy(){
Object target = new PersonDaoImpl();
Transaction transaction = new Transaction();
MyInterceptor interceptor = new MyInterceptor(target, transaction);
/**
* 参数说明:
* 1、目标类的类加载器
* 2、目标类实现的所有的接口
* 3、拦截器
*/
PersonDao personDao = (PersonDao)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), interceptor);
//personDao.savePerson();
personDao.updatePerson();
}
}
注意几个问题:
a、拦截器的invoke方法是在时候执行的?
当在客户端,代理对象调用方法的时候,进入到了拦截器的invoke方法。
b、代理对象的方法体的内容是什么?
拦截器的invoke方法的内容就是代理对象的方法的内容
c、拦截器中的invoke方法中的参数method是谁在什么时候传递过来的?
代理对象调用方法的时候,进入了拦截器中的invoke方法,所以invoke方法中的参数method就是
代理对象调用的方法。
我们看看动态代理有什么问题:
a.在拦截器中除了能调用目标对象的目标方法以外,功能是比较单一的,在这个例子中只能处理事务。
这里可以有个解决方案。比如我们有 Transaction(事务),Log(打印),Access(权限控制)
我们可以让这几个类同时实现一个接口,我们传一个接口的List过来,然后for循环处理。
这也是种解决方案。
b.拦截器中的invoke方法的if判断方法名称在真实的开发环境下是不靠谱的,
因为一旦方法很多if语句需要写很多。
我有100个方法就要判断100次,而且名称不能写错。
这个暂时没有好的解决方法,在spring中可以解决。
c.调用时机的问题,我们是在invoke方法之前调用还是在invoke方法之后调用,或者是出异常在调用。
3、基于动态代理中的第一个问题,解决方案:
//给事务、日志等做了一个抽象,而这个抽象就是Interceptor
public interface Interceptor {
public void interceptor();
}
//事务实现了接口,其他的也同样的做法
public class Transaction implements Interceptor{
public void interceptor() {
System.out.println("begin transaction");
System.out.println("commit");
}
}
//拦截器传的是一个 List<Interceptor>
public class MyInterceptor implements InvocationHandler{
private Object target;//目标类
//除了目标类以外的所有的功能都抽象为Interceptor
private List<Interceptor> interceptors;
public MyInterceptor(Object target, List<Interceptor> interceptors) {
super();
this.target = target;
this.interceptors = interceptors;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
for (Interceptor interceptor : interceptors) {//这样调用了多个功能方法
interceptor.interceptor();
}
method.invoke(target);
return null;
}
}
//使用
@Test
public void testJDKProxy(){
/**
* 1、创建一个目标对象
* 2、创建一个事务
* 3、创建一个拦截器
* 4、动态产生一个代理对象
*/
Object target = new PersonDaoImpl();
Transaction transaction = new Transaction();
List<Interceptor> interceptors = new ArrayList<Interceptor>();
interceptors.add(transaction);//我们可以添加不同的interceptor
MyInterceptor interceptor = new MyInterceptor(target, interceptors);
PersonDao personDao = (PersonDao)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), interceptor);
personDao.updatePerson();
}
4、cglib动态代理:
我们上面说的是jdk的动态代理,是基于接口的,我们的目标类需要实现接口,代理类就实现相同的接口。
cglib代理是基于子类的。
演示下:
//首先需要导入cglib的包
cglib-nodep-2.1_3.jar
//我们现在就没有接口了
public class PersonDaoImpl{
public void savePerson() {
System.out.println("save person");
}
public void updatePerson() {
System.out.println("update person");
}
}
public class Transaction {
public void beginTransaction(){
System.out.println("begin transaction");
}
public void commit(){
System.out.println("commit");
}
}
//我们的拦截器
public class MyInterceptor implements MethodInterceptor{
private Object target;//目标类
private Transaction transaction;
public MyInterceptor(Object target, Transaction transaction) {
super();
this.target = target;
this.transaction = transaction;
}
public Object createProxy(){
//代码增强类
Enhancer enhancer = new Enhancer();
enhancer.setCallback(this);//参数为拦截器
enhancer.setSuperclass(target.getClass());//生成的代理类的父类是目标类
return enhancer.create();
}
public Object intercept(Object arg0, Method method, Object[] arg2,
MethodProxy arg3) throws Throwable {
this.transaction.beginTransaction();
method.invoke(target);
this.transaction.commit();
return null;
}
}
//使用
//通过cglib产生的代理对象,代理类是目标类的子类
public class CGLibProxyTest {
@Test
public void testCGlib(){
Object target = new PersonDaoImpl();
Transaction transaction = new Transaction();
MyInterceptor interceptor = new MyInterceptor(target, transaction);
//使用拦截器创建一个代理的对象
PersonDaoImpl personDaoImpl = (PersonDaoImpl)interceptor.createProxy();
personDaoImpl.savePerson();
}
}
使用cglib的好处是我们目标类不需要实现接口就可以完成动态代理功能了。
讲了这么多,还是没解决多if判断等问题。
5、AOP的一些基本概念:
6、spring AOP的简单例子:
public interface PersonDao {
public void savePerson();
}
public class PersonDaoImpl implements PersonDao{
public void savePerson() {
System.out.println("save person");
}
}
//切面
public class Transaction {
public void beginTransaction(){
System.out.println("begin transaction");
}
public void commit(){
System.out.println("commit");
}
}
public class TransactionTest {
@Test
public void testTransaction(){
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
PersonDao personDao = (PersonDao)context.getBean("personDao");
personDao.savePerson();
}
}
//xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="personDao" class="com.example.spring.aop.xml.transaction.PersonDaoImpl"></bean>
<bean id="transaction" class="com.example.spring.aop.xml.transaction.Transaction"></bean>
<aop:config>
<!--
切入点表达式 确定目标类
-->
<aop:pointcut
expression="execution(* com.example.spring.aop.xml.transaction.PersonDaoImpl.*(..))"
id="perform"/>
<!--
ref指向的对象就是切面
-->
<aop:aspect ref="transaction">
<aop:before method="beginTransaction" pointcut-ref="perform"/>
<aop:after-returning method="commit" pointcut-ref="perform"/>
</aop:aspect>
</aop:config>
</beans>
//测试
public class TransactionTest {
@Test
public void testTransaction(){
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
PersonDao personDao = (PersonDao)context.getBean("personDao");
personDao.savePerson();
}
}
7、spring AOP的原理:
a、当spring容器启动的时候,加载两个bean,对两个bean进行实例化。
b、当spring容器对配置文件解析到<aop:config>的时候,
把切入点表达式解析出来,按照切入点表达式匹配spring容器内容的bean
c、如果匹配成功,则为该bean创建代理对象
d、当客户端利用context.getBean获取一个对象时,如果该对象有代理对象,则返回代理对象
如果没有代理对象,则返回对象本身
8、spring AOP的各种通知:
前置通知:在目标方法执行之前
后置通知:在目标方法执行之后,方法抛出异常以后是不会执行的。
最终通知:类似finally功能,无论目标方法是否抛出异常都将执行。
异常通知:接受目标方法抛出的异常
环绕通知:joinPoint.proceed();这个代码如果在环绕通知中不写,则目标方法不再执行。
可以控制目标方法的执行。
简单的演示下:
public interface PersonDao {
public String savePerson();
}
public class PersonDaoImpl implements PersonDao{
public String savePerson() {
int a = 1/0;//构造一个异常
System.out.println("save person");
return "aaa";
}
}
//切面
public class Transaction {
//前置通知
public void beginTransaction(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("连接点的名称:"+methodName);
System.out.println("目标类:"+joinPoint.getTarget().getClass());
System.out.println("begin transaction");
}
//后置通知,可以使用xml定义的val接受目标方法的返回值
public void commit(JoinPoint joinPoint,Object val){
System.out.println("目标方法的返回值:"+val);
System.out.println("commit");
}
//最终通知
public void finallyMethod(){
System.out.println("finally method");
}
//异常通知,注意 ex 是在xml文件中定义的
public void throwingMethod(JoinPoint joinPoint,Throwable ex){
System.out.println(ex.getMessage());
}
//环绕通知
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("aaaa");
joinPoint.proceed();//调用目标方法
}
}
xml的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="personDao" class="com.example.spring.aop.xml.transaction.PersonDaoImpl"></bean>
<bean id="transaction" class="com.example.spring.aop.xml.transaction.Transaction"></bean>
<aop:config>
<aop:pointcut //切入点
expression="execution(* com.example.spring.aop.xml.transaction.PersonDaoImpl.*(..))"
id="perform"/>
<!--
ref指向的对象就是切面
-->
<aop:aspect ref="transaction">
<aop:before method="beginTransaction" pointcut-ref="perform"/>
<aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>
<aop:after method="finallyMethod" pointcut-ref="perform"/>
<aop:after-throwing method="throwingMethod" throwing="ex" pointcut-ref="perform"/>//定义了一个ex的异常
<aop:around method="aroundMethod" pointcut-ref="perform"/>
</aop:aspect>
</aop:config>
</beans>
注意:前置通知和后置通知能在目标方法的前面和后面加一些代码,但是不能控制目标方法的执行,环绕通知可以控制。