I. Dependencies between methods
As we said in the Spring overview, coupling in programs generally involves coupling between classes, and coupling between methods. We reduce the dependencies between classes through the Ioc container provided by Spring. Today we look at dependencies between methods, using a simulated business code case to analyze problems in the program. Procedure is as follows
Persistence layer interface
public interface AccountMapper {
// Query all methods
List<Account> selectAll(a);
// find the account by id
Account selectById(Integer id);
// Update the account
int update(Account account);
}
Copy the code
Business layer implementation code
package com.zxwin.service.impl;
@Service("accountService")
public class AccountServiceImplOld implements AccountService {
private AccountMapper accountMapper;
// The utility class for transaction management is closed, and the implementation class is not given
private TransactionManage transactionManage;
@Autowired
public void setTransactionManage(TransactionManage transactionManage) {
this.transactionManage = transactionManage;
}
@Autowired
public void setAccountMapper(AccountMapper accountMapper) {
this.accountMapper = accountMapper;
}
@Override
public List<Account> findAllAccount(a) {
List<Account> accounts = null;
// Start the transaction
transactionManage.beginTransaction();
try {
accounts = accountMapper.selectAll();
// Commit the transaction
transactionManage.commit();
} catch (SQLException e) {
e.printStackTrace();
// Rollback if an exception occurs
transactionManage.rollback();
}finally {
// The operation to release resources
transactionManage.release();
}
return accounts;
}
// Transfer operation
@Override
public void transfer(Integer sourceId, Integer targetId, double money) {
transactionManage.beginTransaction();
try {
Account source = accountMapper.selectById(sourceId);
Account target = accountMapper.selectById(targetId);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
accountMapper.update(source);
accountMapper.update(target);
transactionManage.commit();
} catch (SQLException e) {
e.printStackTrace();
transactionManage.rollback();
}finally{ transactionManage.release(); }}}Copy the code
Through the above example, we can find that each method in the business code needs to control transactions through the TransactionManage class, which causes a lot of redundancy in the code and reduces our development efficiency. To make matters worse, if methods in the TransactionManage class are modified (such as parameters) we need to change the code in the entire business layer. This is the dependency that exists between methods.
To reduce this dependency, Spring provides our solution, AOP (aspect oriented programming). Before we get to AOP, we can modify the above example code using the techniques we already know.
The problem with the business in the above example is that there is a lot of duplicate transaction control code and the transaction control code is fixed. We can enhance business classes through dynamic proxy techniques, so that business classes only need to perform normal business operations, and transactional operations are enhanced through proxy classes. Let’s take a look at the dynamic proxy application.
Encapsulating classes for dynamic proxy implementations
@Component
public class ServiceAop {
// The proxy class needs to hold a reference to the proxy class
private AccountService accountService;
private TransactionManage transactionManage;
@Autowired
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
@Autowired
public void setTransactionManage(TransactionManage transactionManage) {
this.transactionManage = transactionManage;
}
// Get the enhanced business proxy class
@Bean("accountServiceProxy")
public AccountService getAccountService(a){
// Use the third tool class to implement the dynamic proxy method through the subclass
return (AccountService) Enhancer.create(accountService.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
// Enhancements: enable transactions
transactionManage.beginTransaction();
try {
// Execute the proxied method
result = method.invoke(accountService,args);
// Enhancements: Commit transactions
transactionManage.commit();
} catch (Exception e) {
e.printStackTrace();
// Enhancements: rollback transactions
transactionManage.rollback();
}finally {
// Enhancement: Release resources
transactionManage.release();
}
returnresult; }}); }}Copy the code
The business layer code only needs to implement the business function
@Service("accountService")
public class AccountServiceImpl implements AccountService {
private AccountMapper accountMapper;
private TransactionManage transactionManage;
@Autowired
public void setTransactionManage(TransactionManage transactionManage) {
this.transactionManage = transactionManage;
}
@Autowired
public void setAccountMapper(AccountMapper accountMapper) {
this.accountMapper = accountMapper;
}
@Override
public List<Account> findAllAccount(a) {
List<Account> accounts = null;
accountMapper.selectAll();
return accounts;
}
@Override
public void transfer(Integer sourceId, Integer targetId, double money) {
Account source = null;
Account target = null; source = accountMapper.selectById(sourceId); target = accountMapper.selectById(targetId); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); accountMapper.update(source); accountMapper.update(target); transactionManage.commit(); }}Copy the code
This allows us to separate the business function from the transaction function through dynamic proxy, and even if the transaction function code changes we only need to change it in the enhancement section.
This is the solution to method dependency, and the above example is very helpful in understanding AOP technology in Spring.
Second, AOP concept
AOP(Aspect Oriented Programming) is a technology that realizes unified maintenance of program through precompilation and dynamic proxy at run time. Using AOP, each part of logic business can be separated, so as to reduce the degree of coupling between each part of logic business and improve the reusability of program. Improve development efficiency.
The purpose of AOP is to implement enhancements to existing functionality without modifying the source code while the program is running;
The advantages of AOP include reduced duplication of code; Improve development efficiency; Maintenance is convenient
The implementation of AOP relies on dynamic proxies, so we must first have some understanding of dynamic proxies, in order to better learn AOP
Terminology in AOP
JoinPoint: Join points are the points that are intercepted, which in Spring are methods (Spring only supports join points of method types). That is, all methods in the proxied object
Pointcut: Pointcut is the definition of which JoinPoints we want to intercept. Methods that need to be enhanced
Advice: Advice is what you do when you are intercepted in JoinPoint. Types of notifications: pre-notification, post-notification, exception notification, final notification, surround notification
Introduction: A special notification allows you to dynamically add methods or fields to a class at run time without modifying the code
Target: The Target object of the agent
Proxy: The resulting Proxy class when a class is woven and enhanced by AOP
Weaving is the process of applying enhancements to target objects to create new proxy objects
Aspect: Is a combination of a pointcut and an introduction
Iv. Classification of notices
Notifications are simply operations enhanced by proxied object methods
There are five main types of notification mentioned in the term above. What does each type of notification mean? Let’s look at the first four.
We know that AOP uses dynamic proxy techniques to implement enhancements to existing functionality while the program is running without modifying the program. Let’s look at an example
// This code implements enhancements in the dynamic proxy
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
System.out.println("Advance notice");
// Execute the pointcut method
result = method.invoke(arithmetic, args);
System.out.println("Post notification");
}catch (Exception e){
e.printStackTrace();
System.out.println("Exception notification");
}finally {
System.out.println("Final notice");
}
returnresult; }};Copy the code
The above example explicitly gives the location of the four types of notification
Pre-notification is the action that takes place before a pointcut method; A post-advice is an action that is executed after the pointcut method has normally executed; Exception notification is an action performed after an exception occurs in a pointcut method; The final notification is executed whether the pointcut method executes correctly or not.
Surround notifications are a way that Spring gives us to manually control when notifications are executed in our code. More on that later.
Five, learning springAOP to clear things
The development phase
Programmers write the core business code
Extract common code and make it into notifications (at the end of development)
In a configuration file, the relationship between declared pointcuts and advice is the aspect
Operation phase
The Spring framework monitors the implementation of the pointcut method. Once the pointcut method is monitored to be executed, the proxy mechanism is used to dynamically create the proxy object of the target object, and the corresponding function of the notification is woven into the corresponding position of the proxy object according to the notification category to complete the complete code logic
Six, AOP case
We simulate the usual development of log printing requirements, the first requirements of Serivce layer work through AOP to achieve log printing function
Dependencies to import
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SpringTest</artifactId>
<version>1.0 the SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13. RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.8. RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<! Parsing pointcut expressions -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
</project>
Copy the code
Let’s start by looking at the business layer code and custom notification classes (classes use methods to implement enhancements to methods in the business layer)
package com.zxwin.service.impl;
// Simulate business layer code
public class UserServiceImpl implements UserService {
@Override
public int updateUser(a) {
System.out.println("Update method executed");
return 0;
}
@Override
public void save(a) {
System.out.println("Save method executed");
}
@Override
public void delete(int i) {
System.out.println("Delete method executed"); }}Copy the code
package com.zxwin.advice;
// Define the notification class
public class Logger {
// The notification method
public void printLog(a){
System.out.println("Logger started printing logs."); }}Copy the code
Let’s look at the specific configuration of AOP
<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">
<! --> < span style = "box-sizing: border-box! Important;
<bean id="userService" class="com.zxwin.service.imple.UserServiceImpl"/>
<! -- Give the notification class to Spring management -->
<bean id="logger" class="com.zxwin.advice.Logger"/>
<! -- Configure AOP 1- use the tag < AOP :config> to start configuring AOP 2- use the tag < AOP :config> under the tag < AOP :config> to start configuring the aspect ID property to uniquely identify the aspect ref property used to execute the notification bean 3- Under the tag < AOP :aspect> use tags of different advice types such as < AOP :before> to start configuring the advice method property to identify which method in the advice class to execute the advice. The pointcut property identifies pointcuts (which methods in the business class need to be enhanced by the advice) The value of pointcut requires a pointcut expression format: the access modifier method returns the package name of the value. Package name Package name Class name Method name (parameter) -->
<aop:config>
<aop:aspect id="loggerAdvice" ref="logger">
<aop:before method="printLog" pointcut="execution( public void com.zxwin.service.imple.UserServiceImpl.save())"/>
</aop:aspect>
</aop:config>
</beans>
Copy the code
The test class
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestAOP {
@Autowired
private UserService userService;
@Test
public void testLoggerAdvice(a){ userService.save(); }}Copy the code
The execution result
Logger starts printing logs and executes the save method
You can see that the print log method is executed before the save() method, indicating that our AOP is configured correctly.
We can also write pointcut expressions as wildcards in the configuration above, as follows
-
Access modifiers can be omitted, and the expression of equivalent to the above execution (void com. Zxwin. Service. Imple. UserServiceImpl. The save ())
-
The method’s return value can use wildcards said any return value type, and amend the expression to execution (* com. Zxwin. Service. Imple. UserServiceImpl. The save ())
-
Package names can use wildcards to represent any package. Note that there are several levels of package that require several *
* *.*.*.*.UserServiceImpl.save()
Copy the code
Package names can also be used as.. Represents any package and subpackage, and can be rewritten as
* *.. UserServiceImpl.save()Copy the code
- Both class and method names can also be represented with wildcards
* *.. *. * ()Copy the code
This writing matches the two methods updateUser() and save() in the UserServiceImpl class.
-
The list of parameters
-
You can write the type name directly
- Primitive types can be written directly to the type name
- Reference types need to be written to the full class name
-
You can also use the wildcard * to indicate any type
* *.. UserServiceImpl.*(*) // Matches the delete(int I) methodCopy the code
-
Can use.. Indicates whether the parameter is valid or not
* *.. UserServiceImpl.*(*) // All three methods will be matchedCopy the code
-
Through the above expression writing learning, we can write the expression * *.. *. * (..) This expression is an all-wildcard character that matches all methods of the current project and is not recommended.
Our usage principle is to match all methods * com.zxwin.service.impl.*.*(..).
7. Configure other types of notification
In the AOP example above, we only configured pre-notification. We know that there are five types of notification in SpringAOP: pre-notification, post-notification, exception notification, final notification, and surround notification. We will then complete the other notifications based on the above example
Notification class modification
public class Logger {
// Pre-notification
public void beforeLog(a){
System.out.println("Pre-notification beforeLog");
}
// post notification
public void afterReturningLog(a){
System.out.println("AfterReturningLog");
}
// Exception notification
public void afterThrowingLog(a){
System.out.println("Exception Notification afterThrowingLog");
}
// Final notification
public void afterLog(a){
System.out.println("Final notification afterLog"); }}Copy the code
Modify section configuration
<aop:config>
<aop:aspect id="loggerAdvice" ref="logger">
<! Configure pointcut expressions that can only be called on the current aspect when placed in the < AOP :aspect> tag. This tag can also be configured under the < AOP :config> tag and can be used by all facets -->
<aop:pointcut id="servicePointcut" expression="execution( * com.zxwin.service.impl.*.*(..) )"/>
<! -- Pre-notification -->
<aop:before method="beforeLog" pointcut-ref="servicePointcut"/>
<! -- Post-notification -->
<aop:after-returning method="afterReturningLog" pointcut-ref="servicePointcut"/>
<! -- Exception notification -->
<aop:after-throwing method="afterThrowingLog" pointcut-ref="servicePointcut"/>
<! -- Final notice -->
<aop:after method="afterLog" pointcut-ref="servicePointcut"/>
</aop:aspect>
</aop:config>
Copy the code
The test class
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestAOP {
@Autowired
private UserService userService;
@Test
public void testLoggerAdvice(a) { userService.save(); }}Copy the code
BeforeLog After the save method is executed afterReturningLog Final notification afterLog
8. Surround notification
Circular notifications in Spring are a way that Spring gives us to manually control when notifications are executed in our code.
Let’s take a look at spring’s configuration for surround notification
<aop:config>
<aop:aspect id="loggerAdvice" ref="logger">
<! Configure pointcut expressions that can only be called on the current aspect when placed in the < AOP :aspect> tag. This tag can also be configured under the < AOP :config> tag and can be used by all facets -->
<aop:pointcut id="servicePointcut" expression="execution( * com.zxwin.service.impl.*.*(..) )"/>
<! -- Surround notification -->
<aop:around method="aroundLog" pointcut-ref="servicePointcut"/>
</aop:aspect>
</aop:config>
Copy the code
We mentioned above that surround notification is actually a way that Spring gives us to manually control notification execution in our code
public class Logger {
// Pre-notification
public void beforeLog(a){
System.out.println("Pre-notification beforeLog");
}
// post notification
public void afterReturningLog(a){
System.out.println("AfterReturningLog");
}
// Exception notification
public void afterThrowingLog(a){
System.out.println("Exception Notification afterThrowingLog");
}
// Final notification
public void afterLog(a){
System.out.println("Final notification afterLog");
}
Spring provides us with the ProceedingJoinPoint interface, which calls the proceed() method of the interface, which quite explicitly calls the pointcut method. Complete the wrap notification */ around the proceed() method
public void aroundLog(ProceedingJoinPoint pjp){
try {
beforeLog();
pjp.proceed(pjp.getArgs());
afterReturningLog();
} catch (Throwable throwable) {
afterThrowingLog();
throwable.printStackTrace();
}finally{ afterLog(); }}}Copy the code
AOP annotation configuration
<! Add @enableAspectJAutoProxy annotations to the config class if you use a pure annotation configuration.
<aop:aspectj-autoproxy/>
Copy the code
@Aspect // Identifies the class as a facet class
@Component("logger")
public class Logger {
// Section expression
@Pointcut("execution(* com.zxwin.service.impl.*.*(..) )"
public void pointcut(a){}
// Pre-notification
@Before("pointcut()")
public void beforeLog(a){
System.out.println("Pre-notification beforeLog");
}
// post notification
@AfterReturning("pointcut()")
public void afterReturningLog(a){
System.out.println("AfterReturningLog");
}
// Exception notification
@AfterThrowing("pointcut()")
public void afterThrowingLog(a){
System.out.println("Exception Notification afterThrowingLog");
}
// Final notification
@After("pointcut()")
public void afterLog(a){
System.out.println("Final notification afterLog");
}
// @around ("pointcut()") does exactly what the other four do
public void aroundLog(ProceedingJoinPoint pjp){
try {
beforeLog();
pjp.proceed(pjp.getArgs());
afterReturningLog();
} catch (Throwable throwable) {
afterThrowingLog();
throwable.printStackTrace();
}finally{ afterLog(); }}}Copy the code
Note: Configuring the other four notifications when using annotation configuration can cause problems with the order of invocation. Surround notification configured with annotations works fine.