Spring source code analysis 6: Spring AOP Overview

ORIGINAL: May Changjei

Why do we want to use AOP

Introduction
year and a half ago wrote an article Spring3: AOP, was learning how to use Spring AOP time to write the basis of comparison. The final recommendation of this article and reply to believe what I wrote to help everyone have a lot of comments, but now from my personal point of view, this article is not well written, and even can be said that not much has content, so these recommendations and comments made me feel deserve.

For these reasons, update an article, from the most basic source code -> use of design patterns (Decorator and Proxy) -> AOP using three levels to explain why we want to use AOP, hope this article to friends friends helpful.

The wording of the original code
if it wants to demonstrate through code, that must be an example, here is my example:

1
has an interface Dao insert has three methods, Delete, update, insert before and after the update is called, the number of milliseconds and several milliseconds before printing calls to call
first define a Dao Interface:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public interface Dao {
 
    public void insert();
     
    public void delete();
     
    public void update();
     
}

Then define an implementation class DaoImpl:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class DaoImpl implements Dao {
 
    @Override
    public void insert() {
        System.out.println("DaoImpl.insert()");
    }
 
    @Override
    public void delete() {
        System.out.println("DaoImpl.delete()");
    }
 
    @Override
    public void update() {
        System.out.println("DaoImpl.update()");
    }
     
}

The most original wording, I want to call insert before and after () and update () method prints each time, you can only define a new class packet layer, the call to insert () method before and after the update () method to handle it separately, new class I named ServiceImpl, its implementation is:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class ServiceImpl {
 
    private Dao dao = new DaoImpl();
     
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }
     
    public void delete() {
        dao.delete();
    }
     
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }
     
}

This is the most original wording, the disadvantage of such an approach is also clear:

Logic output before and after the method call time can not be reused, if there are other places to increase this logic would have to write again
if there are other Dao implementation class, you must add a class to wrap the implementation class, which will result in the number of classes expanding
the decorator pattern
and then we use design patterns, first with the decorator pattern to see how many problems can be resolved. The core decorator pattern is to implement an interface and holds a reference to Dao Dao interface, I will add a class named LogDao, its implementation is:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class LogDao implements Dao {
 
    private Dao dao;
     
    public LogDao(Dao dao) {
        this.dao = dao;
    }
 
    @Override
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }
 
    @Override
    public void delete() {
        dao.delete();
    }
 
    @Override
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }
 
}

When in use, can be used "Dao dao = new LogDao (new DaoImpl ())" manner, advantages of this approach are:

Transparent for the caller, the only know Dao, do not know plus the logging
classes do not expand indefinitely, if the Dao of other class needs to implement the output log, just want to pass the constructor LogDao in different Dao implementation class can
However, this approach also has significant drawbacks and disadvantages are:

Logic output logs still can not reuse
logic and code output logs are coupled to the same output time if I want to delete () method before and after, you need to modify LogDao
However, this approach is the most primitive compared to the wording of the code, already has a very big improvement.

Use a proxy mode
and then we use a proxy mode to try to achieve the most primitive function, use proxy mode, then we will define a InvocationHandler, I named it LogInvocationHandler, its implementation is:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class LogInvocationHandler implements InvocationHandler {
 
    private Object obj;
     
    public LogInvocationHandler(Object obj) {
        this.obj = obj;
    }
     
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("insert".equals(methodName) || "update".equals(methodName)) {
            System.out.println(methodName + "()方法开始时间:" + System.currentTimeMillis());
            Object result = method.invoke(obj, args);
            System.out.println(methodName + "()方法结束时间:" + System.currentTimeMillis());
             
            return result;
        }
         
        return method.invoke(obj, args);
    }
     
}

Its call is very simple, I write a main function:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public static void main(String[] args) {
    Dao dao = new DaoImpl();
         
    Dao proxyDao = (Dao)Proxy.newProxyInstance(LogInvocationHandler.class.getClassLoader(), new Class<?>[]{Dao.class}, new LogInvocationHandler(dao));
         
    proxyDao.insert();
    System.out.println("----------分割线----------");
    proxyDao.delete();
    System.out.println("----------分割线----------");
    proxyDao.update();
}

The results do not demonstrate the advantages of this approach are:

Logic output logs are multiplexed together, if you want to spend another interface logic for the output log, and the second parameter as long as the increase in Class newProxyInstance when the contents of the array can be <?>
Disadvantage of this approach are:

JDK dynamic proxy can only provide an interface for the agent, the agent can not do for the class
code is still a couple, if you want to print the time before and after a call to the delete method, have to increase the delete method of judging in LogInvocationHandler in
use CGLIB
then look at ways to use CGLIB using MethodInterceptor CGLIB only need to implement the interface to:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class DaoProxy implements MethodInterceptor {
 
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
         
        if ("insert".equals(methodName) || "update".equals(methodName)) {
            System.out.println(methodName + "()方法开始时间:" + System.currentTimeMillis());
            proxy.invokeSuper(object, objects);
            System.out.println(methodName + "()方法结束时间:" + System.currentTimeMillis());
             
            return object;
        }
         
        proxy.invokeSuper(object, objects);
        return object;
    }
 
}

Code call for:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public static void main(String[] args) {
    DaoProxy daoProxy = new DaoProxy();
     
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(DaoImpl.class);
    enhancer.setCallback(daoProxy);
         
    Dao dao = (DaoImpl)enhancer.create();
    dao.insert();
    System.out.println("----------分割线----------");
    dao.delete();
    System.out.println("----------分割线----------");
    dao.update();
}

Use CGLIB solve the problem of the JDK Proxy can not do proxy for class, but here to specifically explain a problem: the decorator pattern can be said to be an improvement over the use of native code using the Java proxy can be said for the decorator pattern of an improvement, but not a CGLIB improved for using the Java proxy use.

It can be said in front of the improvement is due to the decorator pattern better than using native code using the Java proxy mode is better off than the decorator, but Java agents and contrast CGLIb and can not be said to improve, because the use is not necessarily CGLIB than Java agents better, both have advantages and disadvantages, like the Spring framework to support both Java proxy and CGLIB two ways.

The moment the code has some better, but I think there are two drawbacks:

Whether using the Java proxy or use CGLIB, write this part of the code is somewhat cumbersome
coupling between code is not resolved, as against to delete () method to add this part of the logic code must be modified

Use AOP

Finally, look at the way the use of AOP, the first time to deal with the definition of a class, I would call it TimeHandler:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class TimeHandler {
     
    public void printTime(ProceedingJoinPoint pjp) {
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature)signature;
            Method method = methodSignature.getMethod();
            System.out.println(method.getName() + "()方法开始时间:" + System.currentTimeMillis());
             
            try {
                pjp.proceed();
                System.out.println(method.getName() + "()方法结束时间:" + System.currentTimeMillis());
            } catch (Throwable e) {
                 
            }
        }
    }
     
}

To the code with the code on line 12 line 8 respectively starts printing method and the method ends execution time of the execution time. I am here to write a little more complex, using The wording, in fact, can be split into versus Two types of this depends on personal preference.

Here to say one more thing, cut printTime method itself can not define any parameters, but some scenes need to get the class call the method, the method signature and other information, then you can define JointPoint, Spring will automatically inject parameters in printTime approach, JoinPoint method gets called by the classes, methods, signature and other information. Because here I use Calls, to ensure that method, so as to output before and after the method call time, and therefore can not be used directly JoinPoint, because JoinPoint not guaranteed method call. At this point you can use ProceedingJoinPoint, ProceedingPointPoint the proceed () method can guarantee method calls, but be aware that, ProceedingJoinPoint only and With other words, if the configuration is aop.xml Then printTime method parameters is then ProceedingJoinPoint, Spring container startup error.

Then look at the aop.xml configuration:

<?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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 
 
http://www.springframework.org/schema/aop
 
 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
 
    <bean id="daoImpl" class="org.xrq.spring.action.aop.DaoImpl" />
    <bean id="timeHandler" class="org.xrq.spring.action.aop.TimeHandler" />
 
    <aop:config>
        <aop:pointcut id="addAllMethod" expression="execution(* org.xrq.spring.action.aop.Dao.*(..))" />
        <aop:aspect id="time" ref="timeHandler">
            <aop:before method="printTime" pointcut-ref="addAllMethod" />
            <aop:after method="printTime" pointcut-ref="addAllMethod" />
        </aop:aspect>
    </aop:config>
     
</beans>

... I do not write expression, do not bother to Baidu, so here intercept all method in the Dao. Test code is very simple:
=
/ **
* @author May Changjei http://www.cnblogs.com/xrq730/p/7003082.html
* /
public class AopTest {

    @Test
    @SuppressWarnings("resource")
    public void testAop() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring/aop.xml");
         
        Dao dao = (Dao)ac.getBean("daoImpl");
        dao.insert();
        System.out.println("----------分割线----------");
        dao.delete();
        System.out.println("----------分割线----------");
        dao.update();
    }
     
}

AOP summary

The results do not demonstrate. I summarize several advantages to this use of AOP:

Content section can be reused, such as TimeHandler of printTime method, anywhere you need to print time after time and methods before method execution, you can use TimeHandler of printTime method
avoids the use of Proxy, CGLIB generate the proxy, all frame work in this area to achieve, developers can focus on the content itself cut
no coupling between the code and the code, if there is a change of method to intercept modify configuration files
below using a diagram to represent what the role of AOP:

Our traditional programming of the programming mode is vertical, i.e. logic to perform additional period of A-> B-> C-> D so go on, after a complete logic. To enhance the function of business code of conduct but under AOP offers another idea, its role is in the business logic without the knowledge (that is, business logic does not need to make any changes) case, this usage scenario, there are many programming ideas, such as transaction commits, before the authority detection method execution, log printing method call events and so on.

AOP usage scenarios, for example
the above examples purely for demonstration use, in order to give you a better understanding of the role of AOP, where the actual scene as an example.

The first example, we know that the default MyBatis transaction is not automatically submitted, so we have to call the programmed time after completion of additions and deletions to the SqlSession commit () method to submit the transaction, which is very troublesome, simply write the following use AOP a piece of code to help us to automatically commit the transaction (I personally tested this code available):

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class TransactionHandler {
 
    public void commit(JoinPoint jp) {
        Object obj = jp.getTarget();
        if (obj instanceof MailDao) {
            Signature signature = jp.getSignature();
            if (signature instanceof MethodSignature) {
                SqlSession sqlSession = SqlSessionThrealLocalUtil.getSqlSession();               
                 
                MethodSignature methodSignature = (MethodSignature)signature;
                Method method = methodSignature.getMethod();
                  
                String methodName = method.getName();
                if (methodName.startsWith("insert") || methodName.startsWith("update") || methodName.startsWith("delete")) {
                    sqlSession.commit();
                }
                 
                sqlSession.close();
            }
        }
    }
     
}

In this scenario we want to use aop label is That the cut after the method call.

Here I made a SqlSessionThreadLocalUtil, every time you open the session, when the current session through SqlSessionThreadLocalUtil SqlSession into ThreadLocal, we see through TransactionHandler, can achieve two functions:

insert, update, delete operation transaction automatically submitted
to SqlSession be close (), so there is no need to close the session in which the business code, because when we write business code will sometimes forget to turn off SqlSession, this may result in the expansion memory handle Therefore this part of the section is also done together
throughout the process, the business code is not known, but TransactionHandler content can be fully re-multiplexing under multiple scenarios.

The second example is an example of access control, whether it is from a security point of view or from a business point of view, we can not all requests are open to all users in a Web system development time, so here we need to do a layer of access control , when we look at the role of AOP must also certainly see AOP can do access control, here I will show you how to use AOP to do access control. We know that a native of Spring MVC, Java classes that implement the Controller interface, based on this, the use of AOP to do access control roughly the following code (this code is purely for some example, I built Maven project is a normal Java project, so there is no validated):

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class PermissionHandler {
 
    public void hasPermission(JoinPoint jp) throws Exception {
        Object obj = jp.getTarget();
         
        if (obj instanceof Controller) {
            Signature signature = jp.getSignature();
            MethodSignature methodSignature = (MethodSignature)signature;
             
            // 获取方法签名
            Method method = methodSignature.getMethod();
            // 获取方法参数
            Object[] args = jp.getArgs();
             
            // Controller中唯一一个方法的方法签名ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
            // 这里对这个方法做一层判断
            if ("handleRequest".equals(method.getName()) && args.length == 2) {
                Object firstArg = args[0];
                if (obj instanceof HttpServletRequest) {
                    HttpServletRequest request = (HttpServletRequest)firstArg;
                    // 获取用户id
                    long userId = Long.parseLong(request.getParameter("userId"));
                    // 获取当前请求路径
                    String requestUri = request.getRequestURI();
                     
                    if(!PermissionUtil.hasPermission(userId, requestUri)) {
                        throw new Exception("没有权限");
                    }
                }
            }
        }
         
    }
     
}

There is no doubt that we want to use the label aop this scenario is . Here I write a very simple, get the current user id and request path, according to both determine whether the user has access to the request, we can understand the meaning.

Postscript
article demonstrates from native code to use AOP process, little by little introduces the advantages and disadvantages of each evolution, and finally practical examples analyzed AOP can do anything.

Micro-channel public number [yellow] small ramp of ants gold dress JAVA engineer, specializing in JAVA
backend technology stack: SpringBoot, SSM family bucket, MySQL, distributed, middleware, services, but also understand the point of finance and investment, adhere to the study and writing, believe in the power of lifelong learning! No reply after public concern "architect" to receive a
Java-based, advanced, project architects and other free learning materials and, more databases, distributed, service and other popular micro learning video technology, rich content, both theory and practice, also presented will be the original author of the study guide Java, Java programmer interview guide and other resources dry goods

Guess you like

Origin www.cnblogs.com/xll1025/p/11407773.html