Framework定制-集成YAHFA框架

本次尝试在Android-9.0.0_r10中集成Hook框架

- 创建hook服务进行hook配置的管理

创建接口文件 /frameworks/base/core/java/android/app/antiy/IAntiyHook.aidl

package android.app.antiy;
 
 
interface IAntiyHook
{
    String readConfig();
    String readFile(String path);
    void writeFile(String path,String data);
}

- 添加接口到编译链  /frameworks/base/Android.bp

"core/java/android/app/antiy/IAntiyHook.aidl",

- 创建服务  /frameworks/base/services/core/java/com/android/server/AntiyHookServer.java

package com.android.server;
 
import android.content.Context;
import android.util.Log;
import android.app.antiy.IAntiyHook;
 
 
import java.io.File;
 
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
 
 
public class AntiyHookService extends IAntiyHook.Stub{
    public final String TAG = "AntiyHookService";
    public Context mContext;
 
    public AntiyHookService(Context c){
        mContext = c;
    }
 
    /*
    * read hook info
    */
    @Override
    public String readConfig(){
        try {
            return readFile("/sdcard/antiy/config.js");
        }catch (Exception e) {
            Log.e(TAG, e.toString());
        }
        return null;
    }
 
    @Override
    public String readFile(String path){
        try{
            return readFileAll(path);
        }catch (Exception e) {
            Log.e(TAG, e.toString());
        }
        return null;
    }
 
    @Override
    public void writeFile(String path,String data){
        try{
            writeTxtToFile(data,path);
        }catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }
 
 
 
    public  String readFileAll(String path) {
        File file = new File(path);
        StringBuilder sb=new StringBuilder();
        if (file != null && file.exists()) {
            InputStream inputStream = null;
            BufferedReader bufferedReader = null;
            try {
                inputStream = new FileInputStream(file);
                bufferedReader = new BufferedReader(new InputStreamReader(
                        inputStream));
                String outData;
                while((outData=bufferedReader.readLine())!=null){
                    sb.append(outData+"\n");
                }
            } catch (Throwable t) {
            } finally {
                try {
                    if (bufferedReader != null) {
                        bufferedReader.close();
                    }
                } catch (Exception e) {
                    Log.e(TAG, "readFileAll" + e.toString());
                }
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (Exception e) {
                    Log.e(TAG, "readFileAll" + e.toString());
                }
            }
        }
        return sb.toString();
    }
 
    // make dir
    public void makeRootDirectory(String filePath) {
        File file = null;
        try {
            Log.d("FileHelper", "makeRootDirectory "+filePath);
            file = new File(filePath);
            if (!file.exists()) {
                boolean isok= file.mkdir();
                Log.d("FileHelper", "makeRootDirectory "+filePath+" "+isok);
            }
        } catch (Exception e) {
            Log.e(TAG, e+"");
        }
    }
 
    // make fike
    public File makeFilePath(String filePath, String fileName) {
        File file = null;
        makeRootDirectory(filePath);
        try {
            file = new File(filePath +"/"+ fileName);
            if (!file.exists()) {
                file.createNewFile();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return file;
    }
 
 
    public void writeTxtToFile(String strcontent, String filePath) {
        String strFilePath = filePath;
        String strContent = strcontent + "\n";  // \r\n 结尾会变成 ^M
        try {
            File file = new File(strFilePath);
            makeFilePath(file.getParent(),file.getName());
            if (!file.exists()) {
                file.getParentFile().mkdirs();
                file.createNewFile();
            }
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            raf.setLength(0);
 
            // 写文件的位置标记,从文件开头开始,后续读取文件内容从该标记开始
            long writePosition = raf.getFilePointer();
            raf.seek(writePosition);
            raf.write(strContent.getBytes());
            raf.close();
        } catch (Exception e) {
            Log.e(TAG,"Error on write File:" + e);
        }
    }
}

- 创建服务管理器  /frameworks/base/core/java/android/app/antiy/AntiyHookManager.java

package android.app.antiy;
 
import android.app.antiy.IAntiyHook;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
 
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
 
 
public class AntiyHookManager {
    public final String TAG = "AntiyHookManager";
    public final IAntiyHook mAntiyHook;
    public Context mContext;
    public HookDex hookdex;
    public HookSo hookso;
 
 
    public AntiyHookManager(Context c, IAntiyHook f){
        mAntiyHook = f;
        mContext = c;
        Log.e(TAG, "AntiyHookManager init" );
    }
 
 
 
    public class HookDex{
        public boolean open = false;
        public String[] packageName;
        public String pluginPath;
        public HookDex(JSONObject js){
            try{
                open = js.getBoolean("open");
                JSONArray names = js.getJSONArray("packageName");
                if (names != null){
                    packageName = new String[names.length()];
                    for (int i = 0; i < names.length(); i++){
                        packageName[i] = names.getString(i);
                    }
                }
                pluginPath = js.getString("pluginPath");
            } catch(Exception e){
                Log.e(TAG, "HookDex:" + e.toString());
            }
 
        }
    }
 
    public class HookSo{
        public boolean open = false;
        public String[] packageName;
        public String pluginPath;
        public HookSo(JSONObject js){
            try{
                open = js.getBoolean("open");
                JSONArray names = js.getJSONArray("packageName");
                if (names != null){
                    packageName = new String[names.length()];
                    for (int i = 0; i < names.length(); i++){
                        packageName[i] = names.getString(i);
                    }
                }
                pluginPath = js.getString("pluginPath");
            } catch(Exception e){
                Log.e(TAG, "HookSo:" + e.toString());
            }
 
        }
    }
 
    public void getConfig() throws RemoteException, JSONException {
        try{
 
            String conf = mAntiyHook.readConfig();
            Log.i(TAG, "getConfig -> " + conf);
            JSONObject arr = new JSONObject(conf);
 
            hookdex = new HookDex(arr.getJSONObject("hookDex"));
            Log.i(TAG, String.valueOf(hookdex.open));
            if (hookdex.packageName != null){
                for (int i = 0; i < hookdex.packageName.length; i++){
                    Log.i(TAG, hookdex.packageName[i]);
                }
            }
            Log.i(TAG, hookdex.pluginPath.toString());
 
            hookso = new HookSo(arr.getJSONObject("hookSo"));
            Log.i(TAG, String.valueOf(hookso.open));
            if (hookso.packageName != null){
                for (int i = 0; i < hookso.packageName.length; i++){
                    Log.i(TAG, hookso.packageName[i]);
                }
            }
            Log.i(TAG, hookso.pluginPath.toString());
 
        } catch (Exception e){
            Log.e(TAG, "getConfig" + e.toString());
        }
 
    }
 
    public boolean checkHookDex(String processName){
        try{
            if (processName != null){
                if (hookdex.open){
                    if (hookdex.packageName != null){
                        for (String s:hookdex.packageName){
                            if (processName.contains(s)){
                                Log.i(TAG, "hook -> " + String.valueOf(processName));
                                return true;
                            }
                        }
                    }
                }
            }
        }catch (Exception e){
            Log.e(TAG, "checkHookDex:" + e.toString());
        }
 
        return false;
    }
 
}

- 注册服务并绑定服务到服务管理器

添加服务名称  /frameworks/base/core/java/android/content/Context.java
public static final String ANTIY_HOOK_SERVICE = "antiy_hook";

- 注册服务  /frameworks/base/services/java/com/android/server/SystemServer.java

traceBeginAndSlog("StartAntiyHookService");
AntiyHookService antiyhookservice= new AntiyHookService(context);
ServiceManager.addService(Context.ANTIY_HOOK_SERVICE, antiyhookservice);
traceEnd();

- 绑定服务  /frameworks/base/core/java/android/app/SystemServiceRegistry.java

registerService(Context.ANTIY_HOOK_SERVICE, AntiyHookManager.class,
        new CachedServiceFetcher<AntiyHookManager>() {
            @Override
            public AntiyHookManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                IBinder b = ServiceManager.getService(Context.ANTIY_HOOK_SERVICE);
                return b == null ? null : new AntiyHookManager(ctx, IAntiyHook.Stub.asInterface(b));
            }});

- 创建hook框架实现类  /frameworks/base/core/java/android/app/AntiyHook.java

package android.app;
 
 
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import dalvik.system.DexClassLoader;
 
 
public class AntiyHook {
    public static final String TAG = "AntiyHook";
 
    static {
        init(Build.VERSION.SDK_INT); //android 9 = 28
    }
 
 
    public static void moveToData(String srcFileName, String dstFileName){
        deleteDataFile(dstFileName);
        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream(srcFileName);
            out = new FileOutputStream(dstFileName);
            byte[] bytes = new byte[1024];
            int i;
            while ((i = in.read(bytes)) != -1)
                out.write(bytes, 0, i);
        } catch (IOException e) {
            Log.e(TAG, e.toString());
        } finally {
            try {
                if (in != null)
                    in.close();
                if (out != null){
                    out.flush();
                    out.close();
                }
            } catch (IOException e) {
                Log.e(TAG, e.toString());
            }
            setPermission(dstFileName);
        }
 
    }
 
    public static void setPermission(String path){
        try{
            File file = new File(path);
            if (file.exists()){
                int perm = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO;
                FileUtils.setPermissions(path, perm, -1, -1);//将权限改为777  
            }
   
        }catch (Exception e) {
            Log.e(TAG, e.toString());
        }
 
    }
 
 
    public static void deleteDataFile(String fileName){
        try {
            File file = new File(fileName);
            if (file.exists()){
                file.delete();
            }
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }
     
    public static void startHook(ClassLoader originLoader, String path){
        try{
            Log.i(TAG, "in AntiyHook startHook");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
                DexClassLoader pluginLoader = new DexClassLoader(
                        path, null, null, originLoader);
                doHookDefault(pluginLoader, originLoader);
            }
        }  catch (Exception e) {
            Log.e(TAG, e.toString());
        }
 
    }
 
 
    public static void doHookDefault(ClassLoader pluginLoader, ClassLoader originLoader) {
        try {
            Log.i(TAG, "in AntiyHook doHookDefault");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
                Class<?> hookInfoClass = Class.forName("com.antiy.HookInfo", true, pluginLoader);
                String[] hookClassNames = (String[]) hookInfoClass.getField("hookClassNames").get(null);
                for (String hookClassName : hookClassNames) {
                    doHookItemDefault(pluginLoader, hookClassName, originLoader);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }
 
    public static void doHookItemDefault(ClassLoader patchClassLoader, String hookItemName, ClassLoader originClassLoader) {
        try {
            Log.i(TAG, "in AntiyHook doHookItemDefault:" + hookItemName);
            Class<?> hookItem = Class.forName(hookItemName, true, patchClassLoader);
 
            String className = (String) hookItem.getField("className").get(null);
            String methodName = (String) hookItem.getField("methodName").get(null);
            String methodSig = (String) hookItem.getField("methodSig").get(null);
 
            if (className == null || className.equals("")) {
                Log.w(TAG, "No target class. Skipping...");
                return;
            }
            Class<?> clazz = Class.forName(className, true, originClassLoader);
            if (clazz == null){
                Log.e(TAG, "Cannot find class " + className);
                return;
            }
 
            Method hook = null;
            Method backup = null;
            for (Method method : hookItem.getDeclaredMethods()) {
                if (method.getName().equals("hook") && Modifier.isStatic(method.getModifiers())) {
                    hook = method;
                } else if (method.getName().equals("backup") && Modifier.isStatic(method.getModifiers())) {
                    backup = method;
                }
            }
            if (hook == null) {
                Log.e(TAG, "Cannot find hook for " + className + ":" + methodName);
                return;
            }
 
            // has to visibly init the classes
            // see the comment for function Utils.initClass()
            if(initClass() != 0) {
                Log.e(TAG, "Utils.initClass failed");
            }
 
            findAndBackupAndHook(clazz, methodName, methodSig, hook, backup);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }
 
    public static void findAndHook(Class targetClass, String methodName, String methodSig, Method hook) {
        hook(findMethod(targetClass, methodName, methodSig), hook);
    }
 
    public static void findAndBackupAndHook(Class targetClass, String methodName, String methodSig,
                                            Method hook, Method backup) {
        backupAndHook(findMethod(targetClass, methodName, methodSig), hook, backup);
    }
 
    public static void hook(Object target, Method hook) {
        backupAndHook(target, hook, null);
    }
 
    public static void backupAndHook(Object target, Method hook, Method backup) {
        if (target == null) {
            throw new IllegalArgumentException("null target method");
        }
         
        if (hook == null) {
            throw new IllegalArgumentException("null hook method");
        }
 
        if (!Modifier.isStatic(hook.getModifiers())) {
            throw new IllegalArgumentException("Hook must be a static method: " + hook);
        }
        checkCompatibleMethods(target, hook, "Original", "Hook");
        if (backup != null) {
            if (!Modifier.isStatic(backup.getModifiers())) {
                throw new IllegalArgumentException("Backup must be a static method: " + backup);
            }
            checkCompatibleMethods(target, backup, "Original", "Backup");
        }
 
        if(initClass() != 0) {
            Log.e(TAG, "initClass failed");
        }
 
        if (!backupAndHookNative(target, hook, backup)) {
            throw new RuntimeException("Failed to hook " + target + " with " + hook);
        }
    }
 
    private static Object findMethod(Class cls, String methodName, String methodSig) {
        if (cls == null) {
            throw new IllegalArgumentException("null class");
        }
        if (methodName == null) {
            throw new IllegalArgumentException("null method name");
        }
        if (methodSig == null) {
            throw new IllegalArgumentException("null method signature");
        }
        return findMethodNative(cls, methodName, methodSig);
    }
 
    private static void checkCompatibleMethods(Object original, Method replacement, String originalName, String replacementName) {
        ArrayList<Class<?>> originalParams;
        if (original instanceof Method) {
            originalParams = new ArrayList<>(Arrays.asList(((Method) original).getParameterTypes()));
        } else if (original instanceof Constructor) {
            originalParams = new ArrayList<>(Arrays.asList(((Constructor<?>) original).getParameterTypes()));
        } else {
            throw new IllegalArgumentException("Type of target method is wrong");
        }
 
        ArrayList<Class<?>> replacementParams = new ArrayList<>(Arrays.asList(replacement.getParameterTypes()));
 
        if (original instanceof Method
                && !Modifier.isStatic(((Method) original).getModifiers())) {
            originalParams.add(0, ((Method) original).getDeclaringClass());
        } else if (original instanceof Constructor) {
            originalParams.add(0, ((Constructor<?>) original).getDeclaringClass());
        }
 
 
        if (!Modifier.isStatic(replacement.getModifiers())) {
            replacementParams.add(0, replacement.getDeclaringClass());
        }
 
        if (original instanceof Method
                && !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) {
            throw new IllegalArgumentException("Incompatible return types. " + originalName + ": " + ((Method) original).getReturnType() + ", " + replacementName + ": " + replacement.getReturnType());
        } else if (original instanceof Constructor) {
            if (replacement.getReturnType().equals(Void.class)) {
                throw new IllegalArgumentException("Incompatible return types. " + "<init>" + ": " + "V" + ", " + replacementName + ": " + replacement.getReturnType());
            }
        }
 
        if (originalParams.size() != replacementParams.size()) {
            throw new IllegalArgumentException("Number of arguments don't match. " + originalName + ": " + originalParams.size() + ", " + replacementName + ": " + replacementParams.size());
        }
 
        for (int i = 0; i < originalParams.size(); i++) {
            if (!replacementParams.get(i).isAssignableFrom(originalParams.get(i))) {
                throw new IllegalArgumentException("Incompatible argument #" + i + ": " + originalName + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i));
            }
        }
    }
 
 
    private static native boolean backupAndHookNative(Object target, Method hook, Method backup);
 
    public static native Object findMethodNative(Class targetClass, String methodName, String methodSig);
 
    private static native void init(int sdkVersion);
 
    public static int initClass() {
        if(shouldVisiblyInit()) {
            long thread = getThread();
            return visiblyInit(thread);
        }
        else {
            return 0;
        }
    }
 
    private static native boolean shouldVisiblyInit();
    private static native int visiblyInit(long thread);
    private static native long getThread();
}

- 创建hook框架类native实现   /frameworks/base/core/jni/android_app_AntiyHook.cpp

#include <nativehelper/JNIHelp.h>
#include "jni.h"
#include <utils/Log.h>
#include <android/log.h>
#include <stdint.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h>
 
 
 
#define NEED_CLASS_VISIBLY_INITIALIZED
#define pointer_size sizeof(void*)
#define roundUpToPtrSize(v) (v + pointer_size - 1 - ((v + pointer_size - 1) & (pointer_size - 1)))
#define read32(addr) *((uint32_t *)(addr))
#define write32(addr, value) *((uint32_t *)(addr)) = (value)
#define readAddr(addr) *((void **)(addr))
#define writeAddr(addr, value) *((void **)(addr)) = (value)
#define TRAMPOLINE_SPACE_SIZE 4096 // 4k mem page size
 
 
#define HOOK_TAG "AntiyHookService"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,HOOK_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,HOOK_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,HOOK_TAG,__VA_ARGS__)
 
 
 
namespace android
{
int SDKVersion;
static uint32_t OFFSET_entry_point_from_interpreter_in_ArtMethod;
static uint32_t OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
static uint32_t OFFSET_ArtMehod_in_Object;
static uint32_t OFFSET_access_flags_in_ArtMethod;
static uint32_t kAccNative = 0x0100;
static uint32_t kAccCompileDontBother = 0x01000000;
static unsigned char *currentTrampolineOff = 0;
static unsigned char *trampolineSpaceEnd = 0;
 
typedef void (*InitClassFunc)(void *, void *, int);
static char *classLinker = NULL;
static InitClassFunc MakeInitializedClassesVisiblyInitialized = NULL;
 
static const size_t kPointerSize = sizeof(void *);
static const size_t kVTablePosition = 2;
 
static unsigned char *allocTrampolineSpace();
 
 
#if defined(__arm__)
// 00 00 9F E5 ; ldr r0, [pc, #0]
// 20 F0 90 E5 ; ldr pc, [r0, 0x20]
// 78 56 34 12 ; 0x12345678 (addr of the hook method)
unsigned char trampoline[] = {
        0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
        0x00, 0x00, 0x9f, 0xe5,
        0x20, 0xf0, 0x90, 0xe5,
        0x78, 0x56, 0x34, 0x12
};
 
// 0c 00 9F E5 ; ldr r0, [pc, #12]
// 01 00 2d e9 ; push {r0}
// 00 00 9F E5 ; ldr r0, [pc, #0]
// 00 80 bd e8 ; pop {pc}
// 78 56 34 12 ; 0x12345678 (addr of the hook method)
// 78 56 34 12 ; 0x12345678 (original entry point of the target method)
unsigned char trampolineForBackup[] = {
        0x0c, 0x00, 0x9f, 0xe5,
        0x01, 0x00, 0x2d, 0xe9,
        0x00, 0x00, 0x9f, 0xe5,
        0x00, 0x80, 0xbd, 0xe8,
        0x78, 0x56, 0x34, 0x12,
        0x78, 0x56, 0x34, 0x12
};
 
#elif defined(__aarch64__)
// 60 00 00 58 ; ldr x0, 12
// 10 00 40 F8 ; ldr x16, [x0, #0x00]
// 00 02 1f d6 ; br x16
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
unsigned char trampoline[] = {
        0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
        0x60, 0x00, 0x00, 0x58,
        0x10, 0x00, 0x40, 0xf8,
        0x00, 0x02, 0x1f, 0xd6,
        0x78, 0x56, 0x34, 0x12,
        0x89, 0x67, 0x45, 0x23
};
 
// 60 00 00 58 ; ldr x0, 12
// 90 00 00 58 ; ldr x16, 16
// 00 02 1f d6 ; br x16
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (original entry point of the target method)
unsigned char trampolineForBackup[] = {
        0x60, 0x00, 0x00, 0x58,
        0x90, 0x00, 0x00, 0x58,
        0x00, 0x02, 0x1f, 0xd6,
        0x78, 0x56, 0x34, 0x12,
        0x89, 0x67, 0x45, 0x23,
        0x78, 0x56, 0x34, 0x12,
        0x89, 0x67, 0x45, 0x23
};
#endif
 
 
void *genTrampoline(void *toMethod, void *entrypoint) {
    size_t trampolineSize = entrypoint != NULL ? sizeof(trampolineForBackup) : sizeof(trampoline);
 
    if(currentTrampolineOff+trampolineSize > trampolineSpaceEnd) {
        currentTrampolineOff = allocTrampolineSpace();
        if (currentTrampolineOff == NULL) {
            return NULL;
        } else {
            trampolineSpaceEnd = currentTrampolineOff + TRAMPOLINE_SPACE_SIZE;
        }
    }
    unsigned char *targetAddr = currentTrampolineOff;
    if(entrypoint != NULL) {
        memcpy(targetAddr, trampolineForBackup, sizeof(trampolineForBackup));
    }
    else {
        memcpy(targetAddr, trampoline,
               sizeof(trampoline));
    }
 
#if defined(__arm__)
    if(entrypoint) {
        memcpy(targetAddr + 20, &entrypoint, pointer_size);
        memcpy(targetAddr + 16, &toMethod, pointer_size);
    }
    else {
        memcpy(targetAddr + 12, &toMethod, pointer_size);
    }
 
#elif defined(__aarch64__)
    if(entrypoint) {
        memcpy(targetAddr + 20, &entrypoint, pointer_size);
        memcpy(targetAddr + 12, &toMethod, pointer_size);
    }
    else {
        memcpy(targetAddr + 16, &toMethod, pointer_size);
    }
#endif
 
    if(entrypoint == NULL) {
        targetAddr += 4;
    }
    currentTrampolineOff += roundUpToPtrSize(trampolineSize);
    return targetAddr;
}
 
 
void setupTrampoline(uint8_t offset) {
 
#if defined(__arm__)
    trampoline[8] = offset;
#elif defined(__aarch64__)
    trampoline[9] |= offset << 4;
    trampoline[10] |= offset >> 4;
#endif
}
 
static unsigned char *allocTrampolineSpace() {
    unsigned char *buf = (unsigned char *)mmap(NULL, TRAMPOLINE_SPACE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
                              MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (buf == MAP_FAILED) {
        LOGE("mmap failed, errno = %s", strerror(errno));
        return NULL;
    }
    else {
        return buf;
    }
 
}
 
 
static int isValidAddress(const void *p) {
    if (!p) {
        return 0;
    }
 
    int ret = 1;
    int fd = open("/dev/random", O_WRONLY);
    size_t len = sizeof(int32_t);
    if (fd != -1) {
        if (write(fd, p, len) < 0) {
            ret = 0;
        }
        close(fd);
    } else {
        ret = 0;
    }
    return ret;
}
 
static int commonFindOffset(void *start, size_t max_count, size_t step, void *value, int start_search_offset) {
    if (NULL == start) {
        return -1;
    }
    if (start_search_offset > (int)max_count) {
        return -1;
    }
 
    for (int i = start_search_offset; i <= (int)max_count; i += step) {
        void *current_value = *(void **) ((size_t) start + i);
        if (value == current_value) {
            return i;
        }
    }
    return -1;
}
 
static int searchClassLinkerOffset(JavaVM *vm, void *runtime_instance, JNIEnv *env, void *libart_handle) {
#ifndef NEED_CLASS_VISIBLY_INITIALIZED
    return -1;
#else
    void *class_linker_vtable = dlsym(libart_handle, "_ZTVN3art11ClassLinkerE");
    if (class_linker_vtable != NULL) {
        class_linker_vtable = (char *) class_linker_vtable + kPointerSize * kVTablePosition;
    }
 
    int jvm_offset_in_runtime = commonFindOffset(runtime_instance, 2000, 4, (void *) vm, 200);
 
    if (jvm_offset_in_runtime < 0) {
        LOGE("failed to find JavaVM in Runtime");
        return -1;
    }
 
    int step = 4;
    int class_linker_offset_value = -1;
    for (int i = jvm_offset_in_runtime - step; i > 0; i -= step) {
        void *class_linker_addr = *(void **) ((uintptr_t) runtime_instance + i);
 
        if (class_linker_addr == NULL || !isValidAddress(class_linker_addr)) {
            continue;
        }
        if (class_linker_vtable != NULL) {
            if (*(void **) class_linker_addr == class_linker_vtable) {
                class_linker_offset_value = i;
                break;
            }
        } else {
            void *intern_table_addr = *(void **) (
                    (uintptr_t) runtime_instance +
                    i -
                    kPointerSize);
            if (isValidAddress(intern_table_addr)) {
                for (int j = 200; j < 500; j += step) {
                    void *intern_table = *(void **) (
                            (uintptr_t) class_linker_addr + j);
                    if (intern_table_addr == intern_table) {
                        class_linker_offset_value = i;
                        break;
                    }
                }
            }
        }
        if (class_linker_offset_value > 0) {
            break;
        }
    }
    return class_linker_offset_value;
#endif
}
 
static int findInitClassSymbols(JNIEnv *env) {
#ifndef NEED_CLASS_VISIBLY_INITIALIZED
    return 1;
#else
    JavaVM *jvm = NULL;
    env->GetJavaVM(&jvm);
 
    void *handle = dlopen("libart.so", RTLD_LAZY);
    if(handle == NULL) {
        LOGE("failed to find libart.so handle");
        return 1;
    }
    else {
        void *runtime_bss = dlsym(handle, "_ZN3art7Runtime9instance_E");
        if(!runtime_bss) {
            LOGE("failed to find Runtime::instance symbol");
            return 1;
        }
        char *runtime = (char*)readAddr(runtime_bss);
        if(!runtime) {
            LOGE("Runtime::instance value is NULL");
            return 1;
        }
 
        int class_linker_offset_in_Runtime = searchClassLinkerOffset(jvm, runtime, env, handle);
 
        if(class_linker_offset_in_Runtime < 0) {
            LOGE("failed to find class_linker offset in Runtime");
            return 1;
        }
 
        classLinker = (char*)readAddr(runtime + class_linker_offset_in_Runtime);
        MakeInitializedClassesVisiblyInitialized = (InitClassFunc)dlsym(handle,
                "_ZN3art11ClassLinker40MakeInitializedClassesVisiblyInitializedEPNS_6ThreadEb");
 
        if(!MakeInitializedClassesVisiblyInitialized) {
            LOGE("failed to find MakeInitializedClassesVisiblyInitialized symbol");
            return 1;
        }
    }
    return 0;
#endif
}
 
jlong __attribute__((naked)) android_app_AntiyHook_getThread(JNIEnv *env, jclass clazz) {
#if defined(__aarch64__)
    __asm__(
            "mov x0, x19\n"
            "ret\n"
            );
#elif defined(__arm__)
    __asm__(
            "mov r0, r9\n"
            "bx lr\n"
            );
#endif
}
 
static int shouldVisiblyInit() {
    return 0;
}
 
jboolean android_app_AntiyHook_shouldVisiblyInit(JNIEnv *env, jclass clazz) {
    return shouldVisiblyInit() != 0;
}
 
jint android_app_AntiyHook_visiblyInit(JNIEnv *env, jclass clazz, jlong thread) {
    if(!shouldVisiblyInit()) {
        return 0;
    }
 
    if(!classLinker || !MakeInitializedClassesVisiblyInitialized) {
        if(findInitClassSymbols(env) != 0) {
            LOGE("failed to find symbols: classLinker %p, MakeInitializedClassesVisiblyInitialized %p",
                 classLinker, MakeInitializedClassesVisiblyInitialized);
            return 1;
        }
    }
 
    MakeInitializedClassesVisiblyInitialized(classLinker, (void *)thread, 1);
    return 0;
}
 
 
 
 
void android_app_AntiyHook_init(JNIEnv *env, jclass clazz, jint sdkVersion) {
    SDKVersion = sdkVersion;
    kAccCompileDontBother = 0x02000000;
    OFFSET_ArtMehod_in_Object = 0;
    OFFSET_access_flags_in_ArtMethod = 4;
 
    OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size;
 
    setupTrampoline(OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
}
 
static uint32_t getFlags(char *method) {
    uint32_t access_flags = (uint32_t)read32(method + OFFSET_access_flags_in_ArtMethod);
    return access_flags;
}
 
static void setFlags(char *method, uint32_t access_flags) {
    write32(method + OFFSET_access_flags_in_ArtMethod, access_flags);
}
 
static void setNonCompilable(void *method) {
 
    uint32_t access_flags = getFlags((char*)method);
    access_flags |= kAccCompileDontBother;
 
    setFlags((char*)method, access_flags);
}
 
static int replaceMethod(void *fromMethod, void *toMethod, int isBackup) {
 
    void *newEntrypoint = NULL;
    if(isBackup) {
        void *originEntrypoint = readAddr((char *) toMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
 
        newEntrypoint = genTrampoline(toMethod, originEntrypoint);
    }
    else {
        newEntrypoint = genTrampoline(toMethod, NULL);
    }
 
    if (newEntrypoint) {
        writeAddr((char *) fromMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod,
                newEntrypoint);
    } else {
        LOGE("failed to allocate space for trampoline of target method");
        return 1;
    }
 
    if (OFFSET_entry_point_from_interpreter_in_ArtMethod != 0) {
        void *interpEntrypoint = readAddr((char *) toMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod);
        writeAddr((char *) fromMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod,
                interpEntrypoint);
    }
 
    if(SDKVersion >= __ANDROID_API_O__) {
        uint32_t access_flags = getFlags((char*)fromMethod);
 
        access_flags |= kAccNative;
 
        setFlags((char*)fromMethod, access_flags);
    }
 
    return 0;
 
}
 
static int doBackupAndHook(void *targetMethod, void *hookMethod, void *backupMethod) {
 
    int res = 0;
 
    setNonCompilable(targetMethod);
    if(backupMethod) setNonCompilable(backupMethod);
 
 
    if (backupMethod) {
        res += replaceMethod(backupMethod, targetMethod, 1);
    }
 
    res += replaceMethod(targetMethod, hookMethod, 0);
 
    return res;
}
 
static void *getArtMethod(JNIEnv *env, jobject jmethod) {
    void *artMethod = NULL;
 
    if(jmethod == NULL) {
        return artMethod;
    }
 
    artMethod = (void *) env->FromReflectedMethod(jmethod);
 
    return artMethod;
 
}
 
jboolean android_app_AntiyHook_backupAndHookNative(JNIEnv *env, jclass clazz,
                                                            jobject target, jobject hook,
                                                            jobject backup) {
    if (!doBackupAndHook(
            getArtMethod(env, target),
            getArtMethod(env, hook),
            getArtMethod(env, backup)
    )) {
        env->NewGlobalRef(hook);
        if(backup) env->NewGlobalRef(backup);
        return JNI_TRUE;
    } else {
        return JNI_FALSE;
    }
}
 
jobject android_app_AntiyHook_findMethodNative(JNIEnv *env, jclass clazz,
                                                        jclass targetClass, jstring methodName,
                                                        jstring methodSig) {
    const char *c_methodName = env->GetStringUTFChars(methodName, NULL);
    const char *c_methodSig = env->GetStringUTFChars(methodSig, NULL);
    jobject ret = NULL;
 
    jmethodID method = env->GetMethodID(targetClass, c_methodName, c_methodSig);
    if (!env->ExceptionCheck()) {
        ret = env->ToReflectedMethod(targetClass, method, JNI_FALSE);
    } else {
        env->ExceptionClear();
        method = env->GetStaticMethodID(targetClass, c_methodName, c_methodSig);
        if (!env->ExceptionCheck()) {
            ret = env->ToReflectedMethod(targetClass, method, JNI_TRUE);
        } else {
            env->ExceptionClear();
        }
    }
 
    env->ReleaseStringUTFChars(methodName, c_methodName);
    env->ReleaseStringUTFChars(methodSig, c_methodSig);
    return ret;
}
 
static const JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
    {"backupAndHookNative", "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z", (void*)android_app_AntiyHook_backupAndHookNative},
    {"findMethodNative", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", (void*)android_app_AntiyHook_findMethodNative},
    {"init", "(I)V", (void*)android_app_AntiyHook_init},
    {"shouldVisiblyInit", "()Z", (void*)android_app_AntiyHook_shouldVisiblyInit},
    {"visiblyInit", "(J)I", (void*)android_app_AntiyHook_visiblyInit},
    {"getThread", "()J", (void*)android_app_AntiyHook_getThread},
};
 
int register_android_app_AntiyHook(JNIEnv* env)
{
    return jniRegisterNativeMethods(env, "android/app/AntiyHook",
                                    sMethods, NELEM(sMethods));
}
 
 
};

- 动态注册native函数  /frameworks/base/core/jni/AndroidRuntime.cpp

REG_JNI(register_android_app_AntiyHook),
extern int register_android_app_AntiyHook(JNIEnv *env);

- 添加native层文件到编译链  /frameworks/base/core/jni/Android.bp

"android_app_AntiyHook.cpp",

- 添加注入点

- /frameworks/base/core/java/android/app/Application

    /* package */ final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
        // add start
        try{
            JLog.print_info("Process-Info", "Application->attach ProcessName:" + getProcessName());
            AntiyHookManager hook = (AntiyHookManager)context.getSystemService(Context.ANTIY_HOOK_SERVICE);
            hook.getConfig();
            if (hook.checkHookDex(getProcessName())){
                String path = "/data/data/" + context.getPackageName() + "/AntiyPlugin.apk";
                AntiyHook.moveToData(hook.hookdex.pluginPath, path);
                AntiyHook.startHook(context.getClassLoader(), path);
            }
        } catch(Exception e){
            Log.e("AntiyHook_Application", e.toString());
        }
        // add end
    }

- 配置Selinux

- system\sepolicy\private\service_contexts   和   system\sepolicy\prebuilts\api\26-28\private\service_contexts
添加:antiy_hook          u:object_r:antiy_hook_service:s0
 
- system\sepolicy\public\service.te  和  system\sepolicy\prebuilts\api\26-28\public\service.te
添加:type antiy_hook_service, app_api_service, system_api_service, system_server_service, service_manager_type;
 
- /system/sepolicy/prebuilts/api/28/public/untrusted_app.te 和 /system/sepolicy/public/untrusted_app.te
新增:allow untrusted_app antiy_hook_service:service_manager find;
新增:allow untrusted_app_27 antiy_hook_service:service_manager find;
新增:allow untrusted_app_25 antiy_hook_service:service_manager find;
 
- 添加sdcard读写执行权限:
 
- /frameworks/base/core/java/com/android/internal/os/ZygoteInit.java 
在forkSystemServer函数setgroups=后面添加1015
 
 -|system   +
            |sepolicy|
                     |private
                             |system_server.te
                                修改:allow system_server sdcard_type:dir { getattr search }; 为 allow system_server sdcard_type:dir { create_dir_perms rw_file_perms};
                                修改:allow system_server configfs:file { getattr open create unlink write }; 为 allow system_server configfs:file { getattr open create unlink write read };
                                新建:allow system_server sdcard_type:file { create_file_perms rw_file_perms };
                                修改:allow system_server media_rw_data_file:file { getattr read write append}; 为 allow system_server media_rw_data_file:file { getattr read write append open };
                                删除:neverallow system_server sdcard_type:dir { open read write };
                                删除:neverallow system_server sdcard_type:file rw_file_perms;
                     |prebuilts|
                               |api|
                                   |26.0|
                                        |private|
                                                |system_server.te //修改同上
                                   |27.0|
                                        |private|
                                                |system_server.te //修改同上
                                   |28.0|
                                        |private|
                                                |system_server.te //修改同上,此文件必须与/sepolicy/private/system_server.te完全一致
 
 
- 添加不信任APP的data目录文件执行权限
    ************************************************************
    以下路径的untrusted_app.te文件中添加下面内容:
    新增:allow untrusted_app app_data_file:file { execute };
    新增:allow untrusted_app_27 app_data_file:file { execute };
    新增:allow untrusted_app_25 app_data_file:file { execute };
    ************************************************************
    -system|
           |sepolicy|
                    |public|
                           |untrusted_app.te
                    |perbuilt|
                             |api|
                                 |28.0|
                                      |public|
                                             |untrusted_app.te
 
 
- 删除不允许执行的权限
    ************************************************************
    以下路径的app_neverallow.te文件中注释掉:
    # neverallow all_untrusted_apps service_manager_type:service_manager add;
    ************************************************************
    -system|
           |sepolicy|
                    |private|
                            |app_neverallow.te
                    |perbuilt|
                             |api|
                                 |28.0|
                                      |private|
                                              |app_neverallow.te

- 编译源码并刷机

- 配置文件config.js需要push到 /sdcard/antiy/路径下

{
    "hookDex":{
        "open":true,                                      //hook开关
        "packageName":[                                   //hook包名列表,如果要hook所有应用,则此项填[]
            "me.wsj.fengyun",
            "com.learn.development",
            "cn.com.essence.stock",
            "com.ss.android.article.news"
        ],
        "pluginPath":"/data/local/tmp/AntiyPlugin.apk"    //hook插件的存放路径,建议不要放在sdcard路径下,否则需要给应用开sdcard权限才能正常hook
    },
    "hookSo":{
        "open":false,
        "packageName":[
            "me.wsj.fengyun",
            "com.learn.development",
            "cn.com.essence.stock"
        ],
        "pluginPath":"/data/local/tmp/hook.txt"
    }
}

- 编写hook插件

插件为一个apk文件形式,此hook框架的原理为,注入此插件中的dex文件,然后遍历插件中 HookInfo类的 hookClassNames 成员,依次对待hook的类的信息进行提取并将待hook的函数地址替换为插件中的回调函数来完成hook


- 插件必须存在类:com.antiy.HookInfo,代码参考为如下:
package com.antiy;
 
import com.antiy.base.Hook_ActivityThread_currentPackageName;
import com.antiy.base.Hook_AntiyHook_startHook;
import com.antiy.base.Hook_DexClassLoader_init;
import com.antiy.hook.Hook_Activity_startActivity;
import com.antiy.hook.Hook_learnApp_AppInfo_getappname;
 
 
public class HookInfo {
    public static String[] hookClassNames = { //待hook的函数列表,将所有需要hook的函数的hook类都写在此变量中
            Hook_ActivityThread_currentPackageName.class.getName(),
            Hook_AntiyHook_startHook.class.getName(),
            Hook_DexClassLoader_init.class.getName(),
            Hook_Activity_startActivity.class.getName(),
            Hook_learnApp_AppInfo_getappname.class.getName()
    };
}


待hook函数的编写形式:

- 非静态函数的hook
package com.antiy.hook;
 
import android.content.Intent;
import android.util.Log;
 
public class Hook_Activity_startActivity {
    public static final String TAG = "AntiyHookPlugin";
    public static String className = "android.app.Activity";          //待hook的函数的类名
    public static String methodName = "startActivity";                //待hook的函数名
    public static String methodSig = "(Landroid/content/Intent;)V";   //函数签名信息
 
    public static void hook(Object thiz, Intent intent) { //当应用中startActivity函数被调用时,将会调用此函数。非静态函数的第一个参数为this指针,所以此处的第一个参数为:Object thiz,此回调函数的参数个数比原函数对一个this指针
        Log.w(TAG, "in Hook_Activity_startActivity hook:" + intent.toString());
        backup(thiz, intent);  //调用原函数(startActivity)
    }
 
    public static void backup(Object thiz, Intent intent) {  //如果要调用原函数,则必须重写此回调函数,如果不需要调用原函数,则不需要重写此函数
        Log.w(TAG, "in Hook_Activity_startActivity_ backup:" + intent.toString());
    }
}
 
 
 
- 静态函数的hook
package com.antiy.base;
 
import android.util.Log;
 
public class Hook_ActivityThread_currentPackageName {
    public static final String TAG = "AntiyHookPlugin";   
    public static String className = "android.app.ActivityThread";  //待hook的函数的类名
    public static String methodName = "currentPackageName";         //待hook的函数名
    public static String methodSig = "()Ljava/lang/String;";        //函数签名信息
 
    public static String hook() {  //静态函数的参数列表与原函数参数列表一致,无需添加this指针
        Log.w(TAG, "in Hook_ActivityThread_currentPackageName hook:");
        return backup();
    }
 
    public static String backup() {
        Log.w(TAG, "in Hook_ActivityThread_currentPackageName backup:");
        return "backup";   //此处返回的值并不影响原函数的返回值,所以你可以认为backup函数内的函数体并不会执行,你只需要重写该函数就行,函数内容可不写
    }
}


- 应用测试

hook头条的startActivity函数

 hook结果

这是一个集成目前Android主流优秀第三方组件、优秀好用的自定义控件、实用工具类封装、 以及一些APP共通模块(比如:版本更新、意见反馈、引导界面等等)的开发包,帮助程序员 快速开发自己的APP 已集成第三方开源组件: 网络请求库android-async-http 图片异步加载库universal-image-loader Sqlite数据库操作ormlite-android 社会化分享ShareSDK+短信验证码 Zxing二维码库 百度地图定位SDK 谷歌依赖注入库RoboGuice WebService调用库ksoap2 XML解析XStream 动画开源库nineoldandroids 表单验证库android-validation-komensky 更多优秀开源库等待集成... 已封装工具类: HTTP网络通信工具类(ToolHTTP.java),get/post请求,支持多种接口回调 SOAP协议通信工具类(ToolSOAP.java),基于异步回调请求WebService接口 Sqlite数据库操作工具类(ToolDatabase.java),获取DAO、创建表、删除表等API 提示信息对话框工具类(ToolAlert.java),已集成泡泡、土司、对话框三种提示 文件操作工具类(ToolFile.java),assets/raw/xml/shrePerface/等文件读写API 地图定位工具类(ToolLocation.java),读取GPS状态、请求定位、获取经纬度等方法 社会化分享工具类(ToolShareSDK.java),各大开发平台分享API操作 短信验证码工具类(ToolMSM.java),移动/联通/电信三网发送手机短信验证码、异步回调验 证结果 字符串操作工具类(ToolString.java),生成UUID、非空非NULL逻辑判断、生成MD5等常用共 通方法 数据操作工具类(ToolData.java),自动递归获取表单数据封装成Map、本地数据分页共通方 法等 图片操作工具类(ToolPicture.java),生成二维码、验证码、灰度、合成、圆角、水印等操 作 读取本地资源工具类(ToolResource.java),反射本地资源文件API,避免依赖R文件,方便 jar形式发布 Android单位转换工具类(ToolUnit.java),sp/dp/px互转 自定义Toast工具类(ToolToast.java),自定义背景色、字体颜色、大小、边框等 Properties操作工具类(ToolProperties.java),读写Properties文件操作 网络操作工具类(ToolNetwork.java),获取网络信息、更改切换网络等相关操作 日期操作工具类(ToolDateTime.java),获取日期、日期加减、格式化日期、日期转换等操作 XML操作工具类(ToolXml.java),基于DOM/XMLPullPaser模式解析、生成XML操作 XMPP操作工具类(ToolXMPP.java),基于XMPP协议的相关API操作 适配字体工具类(ToolAutoFit.java),代码根据设备密度自动缩放View的字体大小 LOG相关工具类(ToolLog.java) 功能待续-->切入记录异常日志,并存储文件或上传至服务 器 已封装/收集自定义控件: 兼容低版本的SwitchButton 追加自定义属性Value的CheckBox/RadioButton/RadioGroup/SingleSpinner 圆角提示信息TipsView 圆角图片RoundImageView 自定义样式风格Progres
### 解决 IntelliJ IDEA 中 `@Autowired` 注解导致的红色波浪线错误 在使用 Spring 框架时,如果遇到 `@Autowired` 注解下的依赖注入对象显示为红色波浪线错误或者黄色警告的情况,通常是由以下几个原因引起的: #### 1. **Spring 插件未启用** 如果 Spring 支持插件未被激活,则可能导致 IDE 无法识别 `@Autowired` 或其他 Spring 特定的功能。可以通过以下方式解决问题: - 打开设置菜单:`File -> Settings -> Plugins`。 - 确认已安装并启用了名为 “Spring Framework Support” 的官方插件[^1]。 #### 2. **项目配置文件缺失或不正确** Spring 需要通过 XML 文件、Java Config 类或其他形式来定义 Bean 定义。如果没有正确加载这些配置文件,可能会导致 `@Autowired` 报错。 - 确保项目的 `applicationContext.xml` 或者基于 Java 的配置类(带有 `@Configuration` 和 `@Bean` 注解)已被正确定义和引入。 - 对于 Spring Boot 项目,确认是否存在 `spring.factories` 文件以及是否包含了必要的组件扫描路径[^3]。 #### 3. **模块依赖关系问题** 当前模块可能缺少对 Spring Core 或 Context 组件库的有效引用。这可能是由于 Maven/Gradle 构建工具中的依赖项声明不足造成的。 - 检查 `pom.xml` (Maven) 或 `build.gradle` (Gradle),确保包含如下核心依赖之一: ```xml <!-- For Maven --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> ``` ```gradle // For Gradle implementation 'org.springframework:spring-context:${springVersion}' ``` - 更新项目依赖树以应用更改:右键点击项目根目录 -> `Maven -> Reload Project` 或运行命令 `./gradlew build --refresh-dependencies`。 #### 4. **IDE 缓存损坏** Intellij IDEA 的缓存机制有时会因各种因素而失效,从而引发误报错误。清除缓存可以有效缓解此类情况。 - 使用快捷组合键 `Ctrl + Alt + Shift + S` 进入项目结构对话框;也可以尝试执行操作序列:`File -> Invalidate Caches / Restart... -> Invalidate and Restart`. #### 5. **启动异常影响正常解析** 若之前存在类似 `com.intellij.diagnostic.PluginException` 的严重初始化失败日志记录,则表明某些关键服务未能成功加载,进而干扰到后续功能表现[^2]。建议重新下载最新稳定版本的 IDEA 并按照标准流程完成初次部署工作。 ```java // 示例代码片段展示如何正确运用 @Autowired 注解实现自动装配 @Service public class StudentService { private final Repository repository; public StudentService(@Qualifier("specificRepository") Repository repo){ this.repository = repo; } } @Component class SpecificComponent{ @Autowired private transient StudentService studentService; // 此处应无任何编译期告警现象发生 } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值