Skip to main content

依赖注入/切面编程

控制反转(IoC)/ 依赖注入(DI)#

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

注册bean到IoC容器#

Component/Service注解#

本框架保持了和Spring常用方式的兼容,但只支持@Component和@Service两个注解,暂未支持@Repository,所以语意上来说,本框架中,注册的bean要么是组件(Component)要么是服务(Service)。

  • 任何一个可以实例化的类加上@Component或@Service注解,即可自动注册到IOC容器中,如果该类拥有父类或接口类,还会自动进行与父类、接口类的映射。
  • 如果接口或父类有多个子实现类,且需要用该父类或接口类型进行依赖注入,那么子实现类注册的时候需要加上名子区别彼此,否则依赖注入时无法确定注入哪一个实例。
  • 任何一个可以实例化的类如果确定为独立使用类,无需和任何父类或接口类映射,也可以不注册,依赖注入时会自动注册该类。
  • 所有注册的类默认会被实例化为单例,如果该类需要每次注入时创建一个新实例,那么需要在该类上加上@Scope(ScopeType.prototype)注解明确指定。

Configuration/Bean注解#

和Spring类似,支持通过@Configuration和@Bean注解进行应用配置,这里配置的bean有最高优先级,可以替换已经通过其他方式注册的类。通过这种方式我们可以在不改动原有设计或模块的情况下,从外部替换注入的组件。 通过这种方式配置bean的时候可以指定bean的initMethod和destroyMethod,分别对应于bean创建后和销毁前。由于尚未实现PostConstruct和PreDestroy注解,目前可以用这种方式替代。

通过程序手动注册#

如果开发过程中需要主动指定映射关系和注册实例,可以通过以下方式进行:

  • 注册映射关系可以通过Ready.beanManager().addMapping系列方法进行操作。

注册映射关系并指定实例,通过以下方法进行操作:

  • Ready.beanManager().addSingletonObject(targetObject); // 直接注册该targetObject的类并映射到targetObject实例
  • Ready.beanManager().addSingletonObject(target.class, targetObject); // 直接注册target类并映射到targetObject实例

如果注册类已经注册过了,则会失败并抛出异常。

Autowired/Inject注解#

@Autowired和@Inject注解都可以实现依赖注入,区别是@Autowired需要搭配@Qualifier注解来选择注入对象,而@Inject可以直接通过名字或类来选择注入对象。

综合实例#

  • 定义一个接口类
public interface Animal {
String type();
int legs();
int height();
int weight();
String walk();
String eat();
String sleep();
}
  • 实现一个Cat类并通过@Component("cat")注解注册,因为我们还会创建多个子类,所以这里注册时加上名称。
@Component("cat")
public class Cat implements Animal {
String type = "cat";
@Override
public String type() {
return type;
}
@Override
public int legs() {
return 4;
}
@Override
public int height() {
return 30;
}
@Override
public int weight() {
return 2;
}
@Override
public String walk() {
return "The cat is walking.";
}
@Override
public String eat() {
return "The cat is eating.";
}
@Override
public String sleep() {
return "The cat is sleeping.";
}
}
  • 实现一个dog类并通过@Service("dog")注解注册,因为Animal有多个子类,所以这里注册时加上名称。
@Service("dog")
@Scope(ScopeType.prototype)
public class Dog implements Animal {
String type = "dog";
@Override
public String type() {
return type;
}
@Override
public int legs() {
return 4;
}
@Override
public int height() {
return 80;
}
@Override
public int weight() {
return 18;
}
@Override
public String walk() {
return "The dog is walking.";
}
@Override
public String eat() {
return "The dog is eating.";
}
@Override
public String sleep() {
return "The dog is sleeping.";
}
}
  • 实现一个pig类并通过@Component注解注册,但这里注册时刻意没有给pig类添加注册名。
@Component
public class Pig implements Animal {
String type = "pig";
@Override
public String type() {
return type;
}
@Override
public int legs() {
return 4;
}
@Override
public int height() {
return 30;
}
@Override
public int weight() {
return 2;
}
@Override
public String walk() {
return "The cat is walking.";
}
@Override
public String eat() {
return "The cat is eating.";
}
@Override
public String sleep() {
return "The cat is sleeping.";
}
}
  • 实现一个Sheep类,但并未进行注解注册。
public class Sheep implements Animal {
String type = "sheep";
@Override
public String type() {
return type;
}
@Override
public int legs() {
return 4;
}
@Override
public int height() {
return 120;
}
@Override
public int weight() {
return 50;
}
@Override
public String walk() {
return "The sheep is walking.";
}
@Override
public String eat() {
return "The sheep is eating.";
}
@Override
public String sleep() {
return "The sheep is sleeping.";
}
public void initMethod() {
System.err.println("Sheep is initializing.");
}
public void destroyMethod() {
System.err.println("Sheep is destroying.");
}
}
  • 创建一个Configuration配置类来注册上面还没有注册的sheep类,并对已经注册的pig类进行替换。
@Configuration
public class MyConfiguration {
@Bean(name={"ram","ewe","lamb","sheep"}, initMethod = "initMethod", destroyMethod = "destroyMethod")
public Animal sheep(){
return new Sheep();
}
@Bean
public Pig pig(){
return new advancedPig();
}
class advancedPig extends Pig {
String type = "advanced pig";
@Override
public String type() {
return type;
}
}
}
  • 创建一个Controller来综合应用以上创建的接口和实现类。
@RequestMapping(value = "/")
public class IocAopController extends Controller {
@Qualifier("cat")
@Autowired
Animal oneAnimal;
@Inject
Cat oneCat;
@Autowired
Dog oneDog;
@Autowired
Dog anotherDog;
@Qualifier("sheep")
@Autowired
Animal oneSheep;
@Qualifier("lamb")
@Autowired
Animal anotherSheep;
@Inject(name = "ram")
Animal ram;
@Inject(name = "ewe")
Animal ewe;
@Inject
Pig pig;
@RequestMapping
public Result<String> index() {
String result = "";
if(oneCat.equals(oneAnimal)) {
result += "The animal is the cat. ";
} else {
result += "The animal is not the cat. ";
}
if(oneDog.equals(anotherDog)) {
result += "The two dogs are the same one. ";
} else {
result += "The two dogs are not the same. ";
}
if(oneSheep.equals(anotherSheep) && ram.equals(ewe) && oneSheep.equals(ram)) {
result += "The four sheep are the same one. ";
} else {
result += "The four sheep are not the same one. ";
}
result += "The type of the pig is " + pig.type() + ". ";
return Success.of(result);
}
}

面向切面编程(AOP)#

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 拦截器是AOP的最典型的应用的方式之一,它可以在不修改原有模块代码的情况下,动态切入模块对目标类或方法进行干预。

简单拦截器#

简单拦截器继承自Jfinal,需要实现一个拦截器并在目标类或方法上添加@Before指向拦截器。从而实现对目标方法执行前和执行后进行干预。

  1. 实现拦截器

创建一个类实现Interceptor接口的intercept方法:

public class FirstInterceptor implements Interceptor {
@Override
public void intercept(Invocation inv) throws Throwable {
System.out.println("before " + inv.getMethodName() + " method execution");
inv.invoke();
inv.setReturnValue(Success.of("654321")); // 改变返回值
System.out.println("after method execution");
}
}
  1. 应用拦截器
@RequestMapping(value = "/")
public class IocAopController extends Controller {
@RequestMapping
@Before(FirstInterceptor.class)
public Result<String> interceptor() {
return Success.of("123456");
}
}

本来该Action输出的是"123456",应用@Before(FirstInterceptor.class)拦截器后,输出变为"654321"。如果需要,也可以通过程序为特定路由添加拦截器。

  1. 全局拦截器(不推荐)

如果需要批量对某些类进行操作,可以不用每个类去添加@Before注解,只需要设计一个拦截器,然后给拦截器加上@GlobalInterceptor全局拦截注解即可。

@GlobalInterceptor(match = "name.contains(\"service.Cat\") || name.contains(\"service.Dog\")")
public class SecondInterceptor implements Interceptor {
@Override
public void intercept(Invocation inv) throws Throwable {
List<String> methods = List.of("walk", "eat");
inv.invoke();
if(methods.contains(inv.getMethodName())){
inv.setReturnValue(((String)inv.getReturnValue()).replace(".", " happily."));
}
}
}

我们来看一下拦截效果

@RequestMapping(value = "/")
public class IocAopController extends Controller {
@RequestMapping
public Result<String> globalInterceptor() {
System.out.println(oneCat.getClass().getName());
return Success.of(oneCat.eat() + oneDog.eat() + oneSheep.eat() + onePig.eat() +
oneCat.walk() + oneDog.walk() + oneSheep.walk() + onePig.walk());
}
}

访问globalInterceptor,可以发现 Cat 和 Dog 的 walk 和 eat 方法被修改了,输出内容多了 "happily" 修饰。

但并不推荐使用全局拦截器,除了性能方面的考虑,下面还有更高级的拦截器可供选择。

自定义注解拦截组件#

除了应用框架集成的拦截器组件,也可以轻松开发自己的拦截器组件。

  1. 定一个注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyAop {
boolean enable() default true;
}
  1. 实现一个简单拦截器
public class MyComponent implements Interceptor {
@Override
public void intercept(Invocation inv) throws Throwable {
MyAop myAop = inv.getMethod().getAnnotation(MyAop.class);
if(myAop == null) {
myAop = inv.getMethod().getDeclaringClass().getAnnotation(MyAop.class);
}
if(myAop.enable()) {
System.err.println("MyComponent is working before method.");
}
inv.invoke();
if(myAop.enable()) {
System.err.println("MyComponent is working after method.");
}
}
}
  1. 注册你的组件
public class Main extends Application {
@Override
protected void initialize() {
Ready.interceptorManager().addAopComponent(
new AopComponent().setAnnotation(MyAop.class).setInterceptorClass(MyComponent.class)
);
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}
  1. 应用你的注解拦截组件

随便找一个组件或控制器,加上你的注解。这里以Cat类的walk方法测试。

@Component("cat")
public class Cat implements Animal {
//....此处省略多行
@Override
@MyAop
public String walk() {
return "The cat is walking. ";
}
//....此处省略多行
}

运行并执行Cat的walk方法,你会看到walk方法被你的组件干预了。

高级拦截器#

上面的简单拦截器拦截的目标类必须是通过框架的依赖注入才能有效,比较受限制。而高级拦截器则可以拦截几乎所有需要拦截的类,没有依赖注入限制。 下面两种方式皆可实现高级拦截器,推荐用第一种方式,还支持通过配置文件进行动态配置。

  1. 实现EasyInterceptor接口类
public class ThirdInterceptor implements EasyInterceptor {
@Override
public void before(Object instance, Method method, Object[] arguments, Class<?>[] parameterTypes) throws Throwable {
System.err.println("ThirdInterceptor before! " + instance.getClass().getCanonicalName());
}
@Override
public Object after(Object instance, Method method, Object[] arguments, Class<?>[] parameterTypes, Object result) throws Throwable {
System.err.println("ThirdInterceptor end");
result = "The pig is awake. "; // 修改返回结果
return result;
}
@Override
public void handleError(Object instance, Method method, Object[] arguments, Class<?>[] parameterTypes, Throwable t) throws Throwable {
System.err.println("ThirdInterceptor error");
}
}

然后在配置文件中添加拦截器配置,设定拦截器以及目标类(Pig)和方法(sleep),如下:

# configuration for dev environment
---
readyWork:
server:
# This is the default binding address.
ip: 0.0.0.0
# Http port if enableHttp is true.
httpPort: 8080
interceptor:
easyDemo:
enabled: true
interceptor: work.ready.examples.ioc_aop.interceptor.ThirdInterceptor
typeInclude:
named: work.ready.examples.ioc_aop.service.Pig
methodInclude:
named: sleep
  1. 继承AbstractMethodInterceptor类,实现以下方法:
public class FourthInterceptor extends AbstractMethodInterceptor {
@Override
protected void before(Object instance, Method method, Object[] arguments, Class<?>[] parameterTypes) throws Throwable {
System.err.println("FourthInterceptor before! " + instance.getClass().getCanonicalName());
}
@Override
protected Object after(Object instance, Method method, Object[] arguments, Class<?>[] parameterTypes, Object result) throws Throwable {
System.err.println("ThirdInterceptor end");
result = ((String)result).replace(" is "," is not "); // 修改返回内容
return result;
}
@Override
protected void handleError(Object instance, Method method, Object[] arguments, Class<?>[] parameterTypes, Throwable t) throws Throwable {
System.err.println("FourthInterceptor error");
}
@Override
public ClassMatch focusOn() {
return new SubTypeMatch(Animal.class)
.and(ElementMatchers.not(ElementMatchers.isInterface()))
.and(ElementMatchers.not(ElementMatchers.<TypeDescription>isAbstract()))
.and(ElementMatchers.not(ElementMatchers.nameContains("$")));
}
@Override
public boolean matches(TypeDescription typeDescription, MethodDescription.InDefinedShape methodDescription, ParameterList<ParameterDescription.InDefinedShape> parameterDescriptions) {
String name = methodDescription.getActualName();
return methodDescription.isPublic() && (name.equals("sleep"));
}
}

由于这种方式不能进行配置设定,所以需要手工加载,这里选择在配置设定的时候一起加载。

public class Main extends Application {
@Override
protected void globalConfig(ApplicationConfig config) {
TransformerManager.getInstance().attach(List.of("work.ready.examples.ioc_aop.interceptor.FourthInterceptor"));
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

动态修改JAVA代码#

本框架提供一个非常便捷的机制对已有组件进行Java代码级别的动态修改。注意不是真的修改原class的Java代码或字节码,是通过AOP手段实现,目标对象必须通过依赖注入才有效。

  1. 实现一个代码注入注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CodeInjector {
boolean enable() default true;
boolean replace() default false;
}
  1. 实现一个代码生成器
public class CodeWorker implements CodeGenerator {
private boolean isEnable = false;
private boolean isReplace = false;
private String methodName;
@Override
public CodeGenerator generatorCode(Class<?> target, Method method, Map<String, Object> clazzData, Map<String, Object> methodData) {
CodeInjector injector = method.getAnnotation(CodeInjector.class);
var worker = new CodeWorker();
worker.methodName = method.getName();
worker.isReplace = injector.replace();
worker.isEnable = injector.enable();
return worker;
}
@Override
public boolean isReplace() {
return isReplace;
}
@Override
public String getInsertCode() {
return isEnable ? "System.err.println(\" before " + methodName + " ==> This is the java code INSERTED here.\");" : null;
}
@Override
public String getReplaceCode() {
return isEnable ? "System.err.println(\" " + methodName + " ==> The original method has been REPLACED by the java code here.\"); returnObject = \"java code replaced\"; " : null;
}
@Override
public String getAppendCode() {
return isEnable ? "System.err.println(\" after " + methodName + " ==> This is the java code APPENDED here.\");" : null;
}
}

生成需要在目标方法前插入的Java代码、方法后追加的Java代码,或者替换整个方法的Java代码。

  1. 注册代码生成组件
public class Main extends Application {
@Override
protected void initialize() {
Ready.proxyManager().addAutoCoder(new JavaCoder()
.setAnnotation(CodeInjector.class)
.setAssignableFrom(Animal.class)
.setGenerator(new CodeWorker()));
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

上面注册了代码生成组件,指定范围是Animal的子类,下面随便找一个Animal的子类的方法,加上CodeInjector注解。

  1. 应用代码生成组件
@Component("cat")
public class Cat implements Animal {
//....此处省略多行
@Override
@CodeInjector(replace = false)
public String eat() {
return "The cat is eating. ";
}
//....此处省略多行
}

我们在Cat类的eat方法上加上注解,可以切换replace = true和false来观察对该方法的修改结果。

更多文档#

以上为本框架提供的常用IoC/AOP应用介绍,还有更多IoC/AOP方面的文档将在后续不断完善,重点内容将以专题形式提供。