Spring Basic Usage - IOC & AOP
What is Spring?
quote from Spring Official Website
Spring makes it easy to create Java enterprise applications.
It provides everything you need to embrace the Java language in an enterprise environment, and with the flexibility to create many kinds of architectures depending on an application needs.
Spring supports a wide range of application scenarios.
In a large enterprise, application often exist for a long time and have to run on a JDK and application server whose upgrade cycle is beyond developer control. Others may run on single jar with the server embedded, possibly in a cloud environment. Yet others may be standalone applications that don’t need a server.
To make it short, every Java engineer need to learn how to work with Spring.
Two major levers Spring provides to us are IOC and AOP
Spring IOC
Before we dive into Spring IOC, first we need to know what is IOC?
Inversion of Control(IOC) is a design pattern in which custom-written portions of a computer program receive the flow of control from a generic framework.
The term “inversion” is as compared to procedural programming.
In procedural programming, a program’s custom code calls reusable library to take care of generic tasks, but with inversion of control, it’s the framework that calls the custom code
Demo
I will introduce two ways Spring provide to us to accomplish IOC – XML file or Annotation way.
Basic Setup
In this article I will leverage Maven as the build system of demo project, and use JDK 1.8
and use Spring 5.3.20 as demo version.
- Introduce necessary dependencies into
pom.xml
file.
<!--pom.xml-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
</dependencies>
- prepare a bean class file, I will use
Person.class
&Address.class
for the purpose of demo, attention: you have to prepare the setters for the class
public class Address {
String state;
String city;
String zipCode;
public void setState(String state) {
this.state = state;
}
public void setCity(String city) {
this.city = city;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
public class Person {
int id;
int age;
String name;
Address address;
List<String> hobbies;
public void setId(int id) {
this.id = id;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAddress(Address address) {
this.address = address;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public String toString(){
// generate this function throw auto-generator
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
", address=" + address +
", habbits=" + habbits +
'}';
}
}
Way 1: XML file
- create file
src/main/resources/ioc.xml
- in
ioc.xml
file, prepare following content
<!--ioc.xml-->
<bean id="addressDemo" class="<Fully_Qualified_Name>.Address">
<property name="state" value="CA"></property>
<property name="city" value="Santa Clara"></property>
<property name="zipcode" value="95051"></property>
</bean>
<bean id="personDemo" class="<Fully_Qualified_Name>.Person">
<property name="name" value="tom"></property>
<property name="age" value="18"></property>
<property name="id" value="1"></property>
<property name="address" ref="addressDemo"></property>
<property name="habbits">
<list>
<value>football</value>
<value>game</value>
</list>
</property>
</bean>
- access to the beans in java program
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person person = context.getBean("personDemo", Person.class);
System.out.println(person);
}
}
- some optimizations
<!--dp.properties-->
username=root
password=123456
url=jdbc:mysql://localhost:3306/demo
driverName=com.mysql.jdbc.Driver
<!---->
<context:property-placeholder location="classpath:db.properties">
</context:property-placeholder>
<bean id="xx" class"xxx">
<property name='username' value="${username}">find username defined in db.properties</property>
</bean>
Way 2: Annotation
- create file
src/main/resources/annotation.xml
<!--annotation.xml-->
<!--introduce the namespace for context in beans tag-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--turn on the package scan-->
<context:component-scan base-package="<Fully_Qualified_Name>"></context:component-scan>
</beans>
- add Annotation on required classes
- use
@Component
/@Repository
/@Service
/@Controller
to annotate the class which you want to leverage Spring container to manage - use
@Autowired
to mark the position where you want to inject the value from Spring container
- use
@Component
public class Address {
}
@Component
public class Person {
@Autowired
private Address address;
}
- access to the bean managed by Spring container
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("annotation.xml");
Person person = context.getBean("person", Person.class);
System.out.println(person);
}
}
Spring AOP
Before we learn Spring AOP, first we need to know what is AOP itself.
AOP, aspect-oriented programming, is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.
It does so by adding behavior to existing code without modifying the code itself.
Instead, separately specifying which code is modified via “pointcut” specification, this allows behaviors that are not central to the business logic (such as logging or security control) to be added to a program without cluttering the code core to the functionality.
just like I did in IOC part, I will introduce two ways to accomplish AOP in Spring – XML and Annotation
I will use a simple calculator for demo purpose, and add necessary loggings to “understand” the healthiness of application.
Basic Setup
- Add necessary dependencies into
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.20.1</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.20</version>
</dependency>
- create file
src/main/resources/aop.xml
<!--aop.xml-->
<!--introduce aop namespaces-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
- Log file preparation
public class LogUtils {
public static void logBeforeFunc(Method method, Object ... args) {
System.out.println(method.getName() + " get executed with args: " + Arrays.asList(args));
}
public static void logAfterFunc(Method method, Object res) {
System.out.println(method.getName() + "complete with result: " +res);
}
}
- Core functions, I will use Calculator as
public class Calculator {
public int add(int i, int j) {
return i + j;
}
public int div(int i, int j) {
return i / j;
}
}
way 1: XML file
- in
aop.xml
file, specify following things
<bean id="logUtil" class="<Full_Qualified_Name>.LogUtils"></bean>
<aop:config>
<aop:aspect ref="logUtil">
<aop:before method="logBeforeFunc" pointcut="execution(Integer <Fully_Qualified_Name>.Calculator.add(int, int))"></aop:before>
<aop:around>...</aop:around>
</aop:aspect>
</aop:config>
- some optimization
<!--extract common expressions into separate pointcut-->
<aop:pointcut id="myPointcut" expression="execution(Integer <Fully_Qualified_Name>.Calculator.add(int, int))"></aop:pointcut>
<aop:aspect ref="logUtil">
<aop:before method="logBeforeFunc" pointcut-ref="myPointcut"></aop:before>
</aop:aspect>
way 2: Annotation
- enable aspect-j in
aop.xml
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- in log utils, use one of following annotations to specify the execution time of pointcut, sorted by the execution order:
@Before
>@After
>@AfterThrowing
/@AfterReturning
@Around
@Aspect
@Component
public class LogUtils {
@Before("execution( public Integer <Fully_Qualified_Name>.Calculator.add(int, int))")
public static void logBeforeFunc(JoinPoint jp) {
Signature signature = jp.getSignature();
Object[] args = jp.getArgs();
System.out.println(signature.getName() + " start executed with args: " + Arrays.asList(args));
}
@AfterReturning(value="execution(int <Fully_Qualified_Name>.Calculator.add(int, int))", returning="res")
public static void logAfterFuncComplete(JoinPoint jp, Object res) {
Signature signature = jp.getSignature();
System.out.println(signature.getName() + " completed with result: " + res);
}
@Around("execution(int <Fully_Qualified_Name>.Calculator.add(int, int))")
public static Object logAroundFunc(ProceedingJoinPoint pjp) {
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
Object res = pjp.proceed(args);
System.out.println(signature.getName() + " with args: " + Arrays.asList(args) + " get result: " + res);
return res;
}
}
then each method will be called automatically
- some optimizations
public class LogUtils {
// can extract the pointcut into a separate function
@PointCut("execution(int <Fully_Qualified_Name>.Calculator.add(int, int))")
public void myPointCut() {}
@Around("myPointCut()")
public void log() {}
// execution formular can be expressed by REGX
// *
// 1. can match 1-more characters, if start with *, then can match all characters
// 2. can match any type of parameter or return type
// 3. can only match one level in path
// 4. cannot match the access modifier, while it can be ignored
// ..
// 1. can match 1 - more parameters
// 2. can match multi-level in path
// so ideally, the simplest expression you can use is
@PointCut("execution(* *(..))")
public void simplestPointCutExpression()
}