Blog: bugstack.cn

Precipitation, share, grow, let yourself and others can gain something! 😄

One, foreword

Why, your code keeps getting stuck on the pigsty?

🎙 how to do, know you are in the Internet, do not know you which big factory. Know you’re working overtime, don’t know what product you’re arguing with. I know you’re slacking off. I don’t know what time you’re going. I know you’re moving bricks. I don’t know which pigsty you’re building.


When you’re working really hard night in and day out, doing the same thing every day, every week, every month, you get minimal growth and minimal rewards. Sweat the most and take the least

Maybe you get excited and start looking at the source code, but you don’t know what to do with it. Look at design patterns, understand when you look, but can’t change your own code. In fact, on the one hand, the knowledge of their own technology stack is not enough, on the other hand, their own reserves of code is not enough. In the end, it is impossible to connect some columns of knowledge, just like you can see HashMap, but also do not think of the sub-table component of the data hash will use HashMap perturbation function idea and Poisson distribution verification, see Spring source code, I can’t read how Mybatis solved the problem of just defining the Dao interface and using configuration or annotations to CRUD the database. It looks like JDK dynamic proxy, and I can’t think of how AOP is designed. Therefore, learning systematically and strengthening the integrity of the knowledge of the technology stack can make better use of these learned coding abilities.

Second, the target

In this chapter, we will move from the implementation of IOC to the development of AOP(Aspect Oriented Programming) content. In the software industry, AOP means: faceted programming, through pre-compilation and runtime dynamic proxy to achieve unified maintenance of program functions and functions. In fact, AOP is the continuation of OOP, in the Spring framework is a very important content, using AOP can be isolated to each part of the business logic, so that the business logic coupling degree between modules is reduced, improve the reusability of the code, but also improve the efficiency of development.

The core technical implementation of AOP is mainly the use of dynamic proxies, as you can give an implementation class of an interface, replace the implementation class with a proxy, and use the proxy class to handle the logic you need. Such as:

@Test
public void test_proxy_class(a) {
    IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserService.class}, (proxy, method, args) -> "You're being represented!");
    String result = userService.queryUserInfo();
    System.out.println("Test Results:" + result);
}
Copy the code

The implementation of proxy classes is familiar, so with a basic idea, you need to think about how to proxy methods, rather than proxy classes. How else to delegate all the methods in a class that conform to certain rules. If you can delegate all of the class’s methods, you can create a method interceptor that adds custom handling to all of the propped methods, such as printing logs, logging time, and monitoring exceptions.

Three,

Before we can incorporate AOP’s entire aspect of design into Spring, we need to solve two problems: how to proxy methods that conform to rules, and how to split the responsibilities of a class once we’ve done the proxy method case. The realization of these two function points is designed and developed with the idea of section. If you are not quite sure what AOP is, you can think of AOP as cutting leeks with a knife. Cutting leeks one by one is always a bit slow. In fact, the program is the same, but leek into a method, a kitchen knife into an interception method. The overall design structure is as follows:

  • As if you were using Spring’s AOP, only handle methods that need to be intercepted. After intercepting the method, perform your extension operation on the method.
  • So we need to first implement a Proxy can Proxy method, in fact, the Proxy method mainly uses the method interceptor class to handle method callsMethodInterceptor#invokeInstead of using Method Method directly as an input parameter to the Invoke Methodmethod.invoke(targetObj, args)This is the difference in the overall usage.
  • In addition to the above core function implementation, also need to useorg.aspectj.weaver.tools.PointcutParserHandling interception expressions"execution(* cn.bugstack.springframework.test.bean.IUserService.*(..) )"With method proxies and process interceptors, we are ready to design an AOP prototype.

Four, implementation,

1. Engineering structure

small-spring-step-11└ ─ ─ the SRC ├ ─ ─ the main │ └ ─ ─ Java │ └ ─ ─ cn. Bugstack. Springframework │ ├ ─ ─ aop │ │ ├ ─ ─ aspectj │ │ │ └ ─ ─ AspectJExpressionPointcut. Java │ │ ├ ─ ─ framework │ │ │ ├ ─ ─ AopProxy. Java │ │ │ ├ ─ ─ the Cglib2AopProxy. Java │ │ │ ├ ─ ─ JdkDynamicAopProxy. Java │ │ │ └ ─ ─ ReflectiveMethodInvocation. Java │ │ ├ ─ ─ AdvisedSupport. Java │ │ ├ ─ ─ ClassFilter. Java │ │ ├ ─ ─ MethodMatcher. Java │ │ ├ ─ ─ Pointcut. Java │ │ └ ─ ─ TargetSource. Java │ ├ ─ ─ beans │ │ ├ ─ ─ factory │ │ │ ├ ─ ─ the config │ │ │ │ ├ ─ ─ AutowireCapableBeanFactory. Java │ │ │ │ ├ ─ ─ BeanDefinition. Java │ │ │ │ ├ ─ ─ spring BeanFactoryPostProcessor. Java │ │ │ │ ├ ─ ─ BeanPostProcessor. Java │ │ │ │ ├ ─ ─ BeanReference. Java │ │ │ │ ├ ─ ─ ConfigurableBeanFactory. Java │ │ │ │ └ ─ ─ SingletonBeanRegistry. Java │ │ │ ├ ─ ─ support │ │ │ │ ├ ─ ─ AbstractAutowireCapableBeanFactory. Java │ │ │ │ ├ ─ ─ AbstractBeanDefinitionReader. Java │ │ │ │ ├ ─ ─ AbstractBeanFactory. Java │ │ │ │ ├ ─ ─ BeanDefinitionReader. Java │ │ │ │ ├ ─ ─ BeanDefinitionRegistry. Java │ │ │ │ ├ ─ ─ CglibSubclassingInstantiationStrategy. Java │ │ │ │ ├ ─ ─ DefaultListableBeanFactory. Java │ │ │ │ ├ ─ ─ DefaultSingletonBeanRegistry. Java │ │ │ │ ├ ─ ─ DisposableBeanAdapter. Java │ │ │ │ ├ ─ ─ FactoryBeanRegistrySupport. Java │ │ │ │ ├ ─ ─ InstantiationStrategy. Java │ │ │ │ └ ─ ─ SimpleInstantiationStrategy. Java │ │ │ ├ ─ ─ support │ │ │ │ └ ─ ─ XmlBeanDefinitionReader. Java │ │ │ ├ ─ ─ Aware. Java │ │ │ ├ ─ ─ BeanClassLoaderAware. Java │ │ │ ├ ─ ─ the BeanFactory. Java │ │ │ ├ ─ ─ BeanFactoryAware. Java │ │ │ ├ ─ ─ BeanNameAware. Java │ │ │ ├ ─ ─ ConfigurableListableBeanFactory. Java │ │ │ ├ ─ ─ DisposableBean. Java │ │ │ ├ ─ ─ FactoryBean. Java │ │ │ ├ ─ ─ HierarchicalBeanFactory. Java │ │ │ ├ ─ ─ InitializingBean. Java │ │ │ └ ─ ─ ListableBeanFactory. Java │ │ ├ ─ ─ BeansException. Java │ │ ├ ─ ─ PropertyValue. Java │ │ └ ─ ─ PropertyValues. Java │ ├ ─ ─ the context │ │ ├ ─ ─ event │ │ │ ├ ─ ─ AbstractApplicationEventMulticaster. Java │ │ │ ├ ─ ─ ApplicationContextEvent. Java │ │ │ ├ ─ ─ ApplicationEventMulticaster. Java │ │ │ ├ ─ ─ ContextClosedEvent. Java │ │ │ ├ ─ ─ ContextRefreshedEvent. Java │ │ │ └ ─ ─ SimpleApplicationEventMulticaster. Java │ │ ├ ─ ─ support │ │ │ ├ ─ ─ AbstractApplicationContext. Java │ │ │ ├ ─ ─ AbstractRefreshableApplicationContext. Java │ │ │ ├ ─ ─ AbstractXmlApplicationContext. Java │ │ │ ├ ─ ─ ApplicationContextAwareProcessor. Java │ │ │ └ ─ ─ ClassPathXmlApplicationContext. Java │ │ ├ ─ ─ ApplicationContext. Java │ │ ├ ─ ─ ApplicationContextAware. Java │ │ ├ ─ ─ ApplicationEvent. Java │ │ ├ ─ ─ ApplicationEventPublisher. Java │ │ ├ ─ ─ ApplicationListener. Java │ │ └ ─ ─ ConfigurableApplicationContext. Java │ ├ ─ ─ core. IO │ │ ├ ─ ─ ClassPathResource. Java │ │ ├ ─ ─ DefaultResourceLoader. Java │ │ ├ ─ ─ FileSystemResource. Java │ │ ├ ─ ─ the Resource. The Java │ │ ├ ─ ─ ResourceLoader. Java │ │ └ ─ ─ UrlResource. Java │ └ ─ ─ utils │ └ ─ ─ ClassUtils. Java └ ─ ─ the test └ ─ ─ Java └ ─ ─ cn. Bugstack. Springframework. Test ├ ─ ─ bean │ ├ ─ ─ IUserService. Java │ ├ ─ ─ UserService. Java │ └ ─ ─ UserServiceInterceptor. Java └ ─ ─ ApiTest. JavaCopy the code

Project source: public account “bugStack wormhole stack”, reply: Spring column, get the full source code

AOP pointcut expressions and usage and dynamic proxy class relationships based on JDK and CGLIB, as shown in Figure 12-2

  • The entire class diagram is where AOP implements the core logic, the top part of which is about the matching implementation of methods, and the bottom part is about the proxy operation of methods, starting with AopProxy.
  • AspectJExpressionPointcut the core functions of relying mainly on aspectj Pointcut, ClassFilter components and processing, and MethodMatcher interface implementation, dedicated to handle classes and methods of matching filter operation.
  • AopProxy is the abstract object of proxy. Its implementation is mainly based on JDK proxy and Cglib proxy. In the previous section on object instantiation CglibSubclassingInstantiationStrategy, we also used provided additional functionality.

2. Proxy method cases

In the implementation of the core functions of AOP, we do a proxy method of the case, through such a can summarize the proxy method of the core panorama, you can better understand the follow-up dismantling of each method, designed to decouple the function of AOP implementation process.

Unit testing

@Test
public void test_proxy_method(a) {
    // Target object (can be replaced with any target object)
    Object targetObj = new UserService();
    / / AOP agent
    IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {
        // method matcher
        MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..) )");
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (methodMatcher.matches(method, targetObj.getClass())) {
                // method interceptor
                MethodInterceptor methodInterceptor = invocation -> {
                    long start = System.currentTimeMillis();
                    try {
                        return invocation.proceed();
                    } finally {
                        System.out.println("Monitor - Begin By AOP");
                        System.out.println("Method name:" + invocation.getMethod().getName());
                        System.out.println("Method time:" + (System.currentTimeMillis() - start) + "ms");
                        System.out.println("Monitor -end \r\n"); }};// reflection calls
                return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
            }
            returnmethod.invoke(targetObj, args); }}); String result = proxy.queryUserInfo(); System.out.println("Test Results:" + result);
}
Copy the code
  • First of all, the goal of the whole case is to give a UserService as the target object, to intercept all methods in the class and add monitoring information printing processing.
  • From the case you can see that there is a Proxy implementation proxy.newProxyInstance, a Method matching MethodMatcher, a reflection invoke(Object Proxy, Method Method, Object[] args), Also use the user’s own interception method after the operation. This looks very similar to what we use with AOP, except that the framework already provides better functionality when you use AOP, and here all the core processes are shown to you.

The test results

Monitor -begin By AOP method name: queryUserInfo method time: 86ms monitor -end test result: small Fu Ge,100001, Shenzhen Process finished with exit code0
Copy the code
  • As you can see from the test results, we have implemented the interception monitoring operation for the UserService#queryUserInfo method. In fact, the AOP we will implement later is the present result, but we need to decouple this part of the test case into more extensible module implementations.

Dismantling case

  • As shown in Screenshot 12-3, we need to unpack the proxy object because it can be either a JDK implementation or a Cglib process.
  • In fact, the method matcher operation is already a separate implementation class, but we also need to put the incoming target object, method matching, interception method, all unified packaging, convenient for external calls to carry out an entry pass.
  • And in the end it’s actuallyReflectiveMethodInvocationThe use of it is currently already implementedMethodInvocationA wrapped class of interfaces, with parameter information including the called object, the called method, and the called input parameter.

3. Tangent expression

Defines the interface

cn.bugstack.springframework.aop.Pointcut

public interface Pointcut {

    /**
     * Return the ClassFilter for this pointcut.
     * @return the ClassFilter (never <code>null</code>)
     */
    ClassFilter getClassFilter(a);

    /**
     * Return the MethodMatcher for this pointcut.
     * @return the MethodMatcher (never <code>null</code>)
     */
    MethodMatcher getMethodMatcher(a);

}
Copy the code
  • A pointcut interface that defines two classes for getting ClassFilter and MethodMatcher, both of which are provided by pointcut expressions.

cn.bugstack.springframework.aop.ClassFilter

public interface ClassFilter {

    /**
     * Should the pointcut apply to the given interface or target class?
     * @param clazz the candidate target class
     * @return whether the advice should apply to the given target class
     */
    boolean matches(Class
        clazz);

}
Copy the code
  • Defines class matching classes that are used to pointcut to find the given interface and target class.

cn.bugstack.springframework.aop.MethodMatcher

public interface MethodMatcher {

    /**
     * Perform static checking whether the given method matches. If this
     * @return whether or not this method matches statically
     */
    boolean matches(Method method, Class
        targetClass);
    
}
Copy the code
  • Method matching to find the target class and method matching in the scope of the expression. This is reflected in the case above:methodMatcher.matches(method, targetObj.getClass())

Implement the pointcut expression class

public class AspectJExpressionPointcut implements Pointcut.ClassFilter.MethodMatcher {

    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();

    static {
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    }

    private final PointcutExpression pointcutExpression;

    public AspectJExpressionPointcut(String expression) { PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITI VES,this.getClass().getClassLoader());
        pointcutExpression = pointcutParser.parsePointcutExpression(expression);
    }

    @Override
    public boolean matches(Class
        clazz) {
        return pointcutExpression.couldMatchJoinPointsInType(clazz);
    }

    @Override
    public boolean matches(Method method, Class
        targetClass) {
        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }

    @Override
    public ClassFilter getClassFilter(a) {
        return this;
    }

    @Override
    public MethodMatcher getMethodMatcher(a) {
        return this; }}Copy the code
  • Pointcut expressions implement the Pointcut, ClassFilter, and MethodMatcher interfaces that define methods, and this class is primarily used for expression validation methods provided with the AspectJ package.
  • Match matches:pointcutExpression.couldMatchJoinPointsInType(clazz),pointcutExpression.matchesMethodExecution(method).alwaysMatches(), this part of the content can be tested separately.

Match the validation

@Test
public void test_aop(a) throws NoSuchMethodException {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.UserService.*(..) )");
    Class<UserService> clazz = UserService.class;
    Method method = clazz.getDeclaredMethod("queryUserInfo");   

    System.out.println(pointcut.matches(clazz));
    System.out.println(pointcut.matches(method, clazz));          
    
    / / true, true
}
Copy the code
  • Here is a separate validation test for matching methods to see if the method you intercept matches the corresponding object.

4. Package cutting notification information

cn.bugstack.springframework.aop.AdvisedSupport

public class AdvisedSupport {

    // Target object to be proxied
    private TargetSource targetSource;
    // method interceptor
    private MethodInterceptor methodInterceptor;
    // Method matcher (check if the target method meets the notification condition)
    private MethodMatcher methodMatcher;
    
    / /... get/set
}
Copy the code
  • AdvisedSupport is mainly used to wrap the properties of Proxy, intercept, and match into a class for easy use in Proxy implementation classes.This is the same thing that you do with your business development
  • TargetSource, which is a target Object, provides the Object entry attribute in the target Object class and gets the TargetClass information.
  • MethodInterceptor is a concrete method implementation class. The user implements the MethodInterceptor#invoke method and does the specific handling.In the case of this article, we do method monitoring
  • MethodMatcher, it is a matching method of operation, the object is provided by AspectJExpressionPointcut service.

5. Proxy Abstract implementation (JDK&Cglib)

Defines the interface

cn.bugstack.springframework.aop.framework

public interface AopProxy {

    Object getProxy(a);

}
Copy the code
  • Define a standard interface for getting proxy classes. Because proxies can be implemented in either the JDK or Cglib, defining interfaces makes it easier to manage implementation classes.

cn.bugstack.springframework.aop.framework.JdkDynamicAopProxy

public class JdkDynamicAopProxy implements AopProxy.InvocationHandler {

    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy(a) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
            MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
            return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));
        }
        returnmethod.invoke(advised.getTargetSource().getTarget(), args); }}Copy the code
  • JDK based proxy class, need to implement the interface AopProxy, InvocationHandler, so that the proxy object getProxy and reflection call method invoke can be handled separately.
  • The getProxy method is an operation to proxy an object and needs to provide inputs to ClassLoader, AdvisedSupport, and the current this class, which provides the Invoke method.
  • Invoke calls methodInterceptor.invoke using the user-provided method that intercepts the implementation after processing the matching method.
  • There is also a ReflectiveMethodInvocation, it is one into the other packaging information, provides the reference object: the target object, method, the ginseng.

cn.bugstack.springframework.aop.framework.Cglib2AopProxy

public class Cglib2AopProxy implements AopProxy {

    private final AdvisedSupport advised;

    public Cglib2AopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy(a) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
        enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }

    private static class DynamicAdvisedInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
            if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
                return advised.getMethodInterceptor().invoke(methodInvocation);
            }
            returnmethodInvocation.proceed(); }}private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

        @Override
        public Object proceed(a) throws Throwable {
            return this.methodProxy.invoke(this.target, this.arguments); }}}Copy the code
  • Classes based on Cglib that use Enhancer proxies can handle proxy object generation of objects at run time for interfaces using underlying ASM bytecode enhancement techniques, so the proxied classes do not need to implement any interfaces.
  • The extended user interception method is handled in Enhancer#setCallback, which is the user’s own new interception handling. Here you can see DynamicAdvisedInterceptor# intercept do after matching the corresponding reflection.

Five, the test

1. Prepare

public class UserService implements IUserService {

    public String queryUserInfo(a) {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Xiao Fuge, 100001, Shenzhen";
    }

    public String register(String userName) {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Registered User: + userName + "Success!"; }}Copy the code
  • There are two different methods available in UserService, and you can add new classes to add tests. Later in our test process, we will add our interception processing to these two methods, printing the method execution time.

2. Customize the interception method

public class UserServiceInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            System.out.println("Monitor - Begin By AOP");
            System.out.println("Method name:" + invocation.getMethod());
            System.out.println("Method time:" + (System.currentTimeMillis() - start) + "ms");
            System.out.println("Monitor -end \r\n"); }}}Copy the code
  • The custom interceptor method requires the invoke method that implements the MethodInterceptor interface, using much the same way Spring AOP does, wrapping the Invocation.proceed() release and adding monitoring information in finally.

Unit testing

@Test
public void test_dynamic(a) {
    // Target object
    IUserService userService = new UserService();     

    // Assemble agent information
    AdvisedSupport advisedSupport = new AdvisedSupport();
    advisedSupport.setTargetSource(new TargetSource(userService));
    advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
    advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..) )"));
    
    // Proxy object (JdkDynamicAopProxy)
    IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
    // Test the call
    System.out.println("Test Results:" + proxy_jdk.queryUserInfo());
    
    // Cglib2AopProxy
    IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
    // Test the call
    System.out.println("Test Results:" + proxy_cglib.register("Flower"));
}
Copy the code
  • The whole case tested AOP’s core code before Spring was incorporated, including what a target object is, how to assemble proxy information, and how to invoke proxy objects.
  • AdvisedSupport, which wraps target objects, user-implemented interception methods, and method matching expressions.
  • Then call JdkDynamicAopProxy and Cglib2AopProxy respectively to see if the method can be successfully intercepted

The test results

Monitor - Begin By AOP method name:public abstractJava. Lang. String cn. Bugstack. Springframework. Test. Beans. IUserService. QueryUserInfo () method is time consuming: 86 ms monitoring - End test results: small Fu Ge,100001Begin By AOP method name:publicJava. Lang. String cn. Bugstack. Springframework. Test. Beans. UserService. Register (Java. Lang. String) method time-consuming: 97 ms monitoring - End test results: Registered users: Huahua Success! Process finished with exit code0
Copy the code
  • Such as AOP function definition, we can through such proxy way, method matching and interception, in the corresponding target method, do interception operation for monitoring information printing.

Six, summarized

  • From the use of Proxy#newProxyInstance and MethodInterceptor#invoke in this article to verify the core principles of the section and then disintegrate the functions into the implementation of Spring framework, it can be seen that a seemingly complicated technology actually has not much core content. But because of the need to decouple and wrap responsibilities in order to accommodate further extensions, the use of this design pattern makes it easier for callers to expand on demand.
  • You can learn how to handle proxy objects, filter methods, intercept methods, and the differences between using Cglib and JDK proxies. This is not just a feature of the Spring framework. This is used in all sorts of other scenarios that require less manual hard coding.Such as RPC, Mybatis, MQ, distributed tasks
  • The use of some core technologies is highly correlated, and they do not exist in isolation. And this can connect the whole stack of technology in series process, need you to a lot of learning, accumulation, from point to surface laying, in order to learn a knowledge point to expand to a knowledge area and knowledge system construction.

Seven, series recommendation

  • Java Face Book PDF, 417 pages 115,000 words
  • Have you been working for two or three years and have no idea what the architecture diagram is?
  • Code farming cloud service use learning, ministry environment, beginning mouth, with domain name, SSL, take blog!
  • I wrote 200,000 lines of code before graduation, which made me a face bully in the eyes of my classmates!
  • Thread.start(), how does it start a Thread?