For the source code of this tutorial, please visit: tutorial_demo
1, What is dynamic agent
1.1 concept
The purpose of dynamic proxy is similar to decoration mode, which is to enhance an object. All cases that use decorator mode can be replaced with dynamic proxies.
Features: bytecode is created on demand and loaded on demand;
Function: enhance the method without modifying the source code;
Learning purpose: to prepare for learning the principle of AOP.
1.2 implementation mode
There are two ways:
- The dynamic proxy based on interface is provided by JDK officially. The proxy class implements at least one interface. If not, it cannot be used;
- Dynamic agent based on subclass, provided by the third-party cglib library.
In this tutorial, we use the interface based dynamic proxy method, which is used in all cases.
1.3. Several concepts to be defined
Target object: the enhanced object.
Proxy object: the target object is required, and then the enhanced object is added to the target object.
Target method: the enhanced method.
Proxy object = target object + enhancement
So far, we need to know that there is a way to enhance the method without changing the target object method. This way is dynamic proxy. With it, we need to provide the target object and the enhanced build proxy object.
Getting a proxy object is equivalent to having an enhanced version of the target object, running the relevant methods, in addition to the running method itself, the enhanced content will also be run, so that the method can be enhanced without changing the source code.
1.4 detailed explanation of dynamic agent mode based on interface
1.4.1 how to generate proxy objects
Use the newProxyInstance method in the Proxy class.
1.4.2. Parameter details of newProxyInstance method
ClassLoader loader:
Class loader type, you don't need to pay attention to it, you just need to know how to get it. Get method:
this.class.getClassLoader();
As long as you have a Class object, you can get the ClassLoader object.
Class[] interfaces:
Specifies which interfaces the object returned by the newProxyInstance() method implements, because it is an array, multiple interfaces can be specified.
InvocationHandler h:
The most important of the three parameters is an interface, called call handler. This interface has only one method, the invoke() method. It is the only implementation of all methods of the proxy object. That is to say, no matter which method you call on the proxy object, you are actually calling the invoke() method of the InvocationHandler.
1.4.3. Details of the parameters of invoke() method
Any interface method that executes the proxied object passes through that method.
Object proxy: proxy object, that is Proxy.newProxyInstance() method, which is usually not used by us.
Method method: indicates the reflection object of the currently called method, for example, m.fun(), then M is the reflection object of fun();
Object[] args: indicates the parameters of the currently called method. Of course, the m.fun() call has no parameters, so args is an array with a length of 0.
2, Dynamic agent case
Here is a case to illustrate the use of dynamic agents.
2.1 create Maven project and add coordinates
<?xml version="1.0" encoding="UTF-8"?> <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.codeaction</groupId> <artifactId>proxy</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies> </project>
2.2. Create an IWaiter interface
package org.codeaction.proxy; //Interface for server public interface IWaiter { //How to provide services void serve(); }
2.3. Create an implementation class of IWaiter interface
package org.codeaction.proxy; //For the waiter public class ManWaiter implements IWaiter { @Override public void serve() { System.out.println("service..."); } }
For the existing problems, I want ManWaiter to print the following information (call the serve method) when providing services:
Hello... Services bye...
We can do this:
package org.codeaction.proxy; //For the waiter public class ManWaiter implements IWaiter { @Override public void serve() { System.out.println("Hello..."); System.out.println("service..."); System.out.println("bye..."); } }
But in this way, we modify the serve method. If there are other requirements in the future, we need to modify the serve method again, which is obviously cumbersome and undesirable. We can use the dynamic agent method to enhance the serve method without modifying the source code.
2.4 create test class and use dynamic agent
package org.codeaction.test; import org.codeaction.proxy.IWaiter; import org.codeaction.proxy.ManWaiter; import org.junit.Test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyTest { @Test public void TestProxy() { //Target object IWaiter manWaiter = new ManWaiter(); /** * Three parameters to create a proxy object */ ClassLoader loader = this.getClass().getClassLoader(); Class[] interfaces = {IWaiter.class}; InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object resultValue = null; System.out.println("Hello..."); resultValue = method.invoke(manWaiter, args);//Call the target method of the target object System.out.println("bye..."); return resultValue; } }; //Get the proxy object, which is the object enhanced on the basis of the target object IWaiter waiter = (IWaiter) Proxy.newProxyInstance(loader, interfaces, handler); //Add "hello" in the front and "goodbye" in the back waiter.serve(); } }
Run the test method with the output as follows:
Hello... Services bye...
Through the above code and running results, we find that:
- Using dynamic agent needs to provide: target object, three parameters;
- The generated proxy object is the object that implements all interfaces of the second parameter of the three parameters;
- The way to run the proxy object is to run the invoke method;
- Implement enhancements in the invoke method.
3, Dynamic agent using agent factory
In the above case, the target object and the enhancement are bound together, unable to switch freely and inflexibly. Next, we create a proxy factory to realize dynamic proxy.
3.1. Create pre enhanced interface
package org.codeaction.proxy; //Pre enhancement public interface BeforeAdvice { void before(); }
3.2 create a post enhanced interface
package org.codeaction.proxy; //Post enhancement public interface AfterAdvice { void after(); }
3.3 create agent factory
package org.codeaction.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * This class is used to generate proxy objects * Required parameters: * * Target object * * enhance * How to use it? * 1.Create agent factory * 2.Set up three things for the factory: * * Target object: setTargetObject(xxx); * * Pre enhancement: setbeforeadvice (implementation of the interface) * * Post enhancement: setafteradvice (implementation of this interface) * 3.Call createProxy() to get the proxy object * * When executing a proxy object method: * > before() of implementing BeforeAdvice * > Target method of target object * > after() of AfterAdvice */ public class ProxyFactory { private Object targetObject;//Target object private BeforeAdvice beforeAdvice;//Pre enhancement private AfterAdvice afterAdvice;//Post enhancement public Object getTargetObject() { return targetObject; } public void setTargetObject(Object targetObject) { this.targetObject = targetObject; } public BeforeAdvice getBeforeAdvice() { return beforeAdvice; } public void setBeforeAdvice(BeforeAdvice beforeAdvice) { this.beforeAdvice = beforeAdvice; } public AfterAdvice getAfterAdvice() { return afterAdvice; } public void setAfterAdvice(AfterAdvice afterAdvice) { this.afterAdvice = afterAdvice; } //Used to generate proxy objects public Object createProxyObject() { //Three parameters ClassLoader classLoader = this.getClass().getClassLoader(); Class[] interfaces = this.targetObject.getClass().getInterfaces(); InvocationHandler handler = new InvocationHandler() { //This is done when the method of the proxy object is called @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object resultValue = null; if(beforeAdvice != null) { //Perform pre enhancement beforeAdvice.before(); } //Target method of executing target object resultValue = method.invoke(targetObject, args); if(afterAdvice != null) { //Perform post enhancement afterAdvice.after(); } //Returns the return value of the target object return resultValue; } }; //Get proxy object return Proxy.newProxyInstance(classLoader, interfaces, handler); } }
3.4 add test method to test class
@Test public void testProxyFactory() { //Create factory ProxyFactory factory = new ProxyFactory(); //Set target object factory.setTargetObject(new ManWaiter()); //Set pre enhancement factory.setBeforeAdvice(new BeforeAdvice() { @Override public void before() { System.out.println("Hello..."); } }); //Set post enhancement factory.setAfterAdvice(new AfterAdvice() { @Override public void after() { System.out.println("bye..."); } }); //Create proxy object IWaiter waiter = (IWaiter) factory.createProxyObject(); //Execute proxy object method waiter.serve(); }
Run the test method with the output as follows:
Hello... Services bye...
4, Use the proxy factory to modify the code in the previous section
In the previous article, we combined pure annotation with Apache Commons The code modification of dbutils to implement CRUD operation of single table has become the version supporting transaction. Every Service method needs to start transaction, commit transaction and roll back transaction code redundancy. If the method name of relevant method in JdbcUtils is modified, then every call location in Service is modified. To solve the above problem, we use dynamic agent to modify the code in the previous section .
4.1. Create pre enhanced interface
package org.codeaction.proxy; public interface BeforeAdvice { void before() throws Exception; }
4.2 create a post enhanced interface
package org.codeaction.proxy; public interface AfterAdvice { void after() throws Exception; }
4.3 create special enhanced interface
This is for rollback. Teach him special enhancement.
package org.codeaction.proxy; public interface ActAdvice { void act() throws Exception; }
4.4. Create agent factory
package org.codeaction.proxy; import org.springframework.stereotype.Component; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @Component public class ProxyFactory { private Object targetObject; private BeforeAdvice beforeAdvice; private AfterAdvice afterAdvice; private ActAdvice actAdvice; public ActAdvice getActAdvice() { return actAdvice; } public void setActAdvice(ActAdvice actAdvice) { this.actAdvice = actAdvice; } public Object getTargetObject() { return targetObject; } public void setTargetObject(Object targetObject) { this.targetObject = targetObject; } public BeforeAdvice getBeforeAdvice() { return beforeAdvice; } public void setBeforeAdvice(BeforeAdvice beforeAdvice) { this.beforeAdvice = beforeAdvice; } public AfterAdvice getAfterAdvice() { return afterAdvice; } public void setAfterAdvice(AfterAdvice afterAdvice) { this.afterAdvice = afterAdvice; } public Object createProxyObject() { ClassLoader classLoader = this.getClass().getClassLoader(); Class[] interfaces = this.targetObject.getClass().getInterfaces(); InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object resultValue = null; try { if(beforeAdvice != null) { beforeAdvice.before(); } resultValue = method.invoke(targetObject, args); if(afterAdvice != null) { afterAdvice.after(); } } catch (Exception e) { e.printStackTrace(); if(actAdvice != null) { actAdvice.act(); } } return resultValue; } }; return Proxy.newProxyInstance(classLoader, interfaces, handler); } }
4.5 modify the implementation class AccountServiceImpl of Service interface
Remove all transaction related code and let the Service focus on business only
package org.codeaction.service.impl; import org.codeaction.dao.IAccountDao; import org.codeaction.domain.Account; import org.codeaction.service.IAccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; @Override public List<Account> findAll() throws Exception { return accountDao.findAll(); } @Override public Account findById(Integer id) throws Exception { return accountDao.findById(id); } @Override public void save(Account account) throws Exception { accountDao.save(account); } @Override public void update(Account account) throws Exception { accountDao.update(account); } @Override public void delete(Integer id) throws Exception { accountDao.delete(id); } @Override public void transfer(Integer srcId, Integer dstId, Float money) throws Exception { Account src = accountDao.findById(srcId); Account dst = accountDao.findById(dstId); if(src == null) { throw new RuntimeException("Transfer out user does not exist"); } if(dst == null) { throw new RuntimeException("Transfer in user does not exist"); } if(src.getMoney() < money) { throw new RuntimeException("Insufficient balance of transfer out account"); } src.setMoney(src.getMoney() - money); dst.setMoney(dst.getMoney() + money); accountDao.update(src); //int x = 1/0; accountDao.update(dst); } }
4.6 modify the main configuration class
package org.codeaction.config; import org.codeaction.proxy.ActAdvice; import org.codeaction.proxy.AfterAdvice; import org.codeaction.proxy.BeforeAdvice; import org.codeaction.proxy.ProxyFactory; import org.codeaction.service.IAccountService; import org.codeaction.util.JdbcUtils; import org.springframework.context.annotation.*; import java.sql.SQLException; @Configuration @ComponentScan(basePackages = "org.codeaction") @PropertySource("classpath:jdbc.properties") @Import(JdbcConfig.class) public class MyConfig { /** * * @param factory Agency factory * @param accountService Target object * @return */ @Bean("proxyAccountService") public IAccountService createProxyAccountService(ProxyFactory factory, IAccountService accountService) { factory.setTargetObject(accountService); factory.setBeforeAdvice(new BeforeAdvice() { @Override public void before() throws Exception { //Open transaction JdbcUtils.beginTransaction(); } }); factory.setAfterAdvice(new AfterAdvice() { @Override public void after() throws Exception { //Commit transaction JdbcUtils.commitTransaction(); } }); factory.setActAdvice(new ActAdvice() { @Override public void act() throws Exception { //RollBACK JdbcUtils.rollbackTransaction(); } }); //Build proxy object return (IAccountService)factory.createProxyObject(); } }
4.7 modify test class
package org.codeaction.test; import org.codeaction.config.MyConfig; import org.codeaction.domain.Account; import org.codeaction.service.IAccountService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = MyConfig.class) public class MyTest { //Injection agent factory object @Autowired @Qualifier("proxyAccountService") private IAccountService accountService; @Test public void testFindAll() throws Exception { List<Account> accounts = accountService.findAll(); for (Account account : accounts) { System.out.println(account); } } @Test public void testFindById() throws Exception { Account account = accountService.findById(3); System.out.println(account); } @Test public void testSave() throws Exception { Account account = new Account(); account.setName("abc"); account.setMoney(10000F); accountService.save(account); System.out.println(account); } @Test public void testDelete() throws Exception { accountService.delete(4); } @Test public void testUpdate() throws Exception { Account account = new Account(); account.setId(5); account.setName("ab111111111c111"); account.setMoney(10000F); accountService.update(account); } @Test public void testTrans() throws Exception { accountService.transfer(1, 2, 10F); } }
Note that the accountServie injected here is the object of the agent factory class, run the test method and test.