目录
-
文章摘要
-
事务管理
在百度百科中对事务的定义就是: 事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
我们这里有个这样的大致印象,事务就是数据库中若干操作组成的,我们今天要做的就是保证组成事务的这一组数据库操作要么全都成功,要么全都不成功。更多的内容也可以参考:https://www.cnblogs.com/liantdev/p/10149443.html
-
Java原生实现事务管理:
实现的是对一个转账操作的事务管理,防止转账的过程中因为异常而导致,一个用户的钱减少了而一个用户的钱却并没有增加,这里就很好的体现了事务管理的原子性与一致性。我们先来准备一些常规代码(实体类,服务层、持久层的接口与实现):
public interface IAccountDao {
public void saveAccount(Account account) throws SQLException;
public Account findAccountByName(String name);
public void updateAccount(Account account);
public void deleteAccountId(int id);
}
public interface IAccountService {
public void saveAccount(Account account);
public Account findAccountByName(String name);
public void updateAccount(Account account);
public void deleteAccountId(int id);
public void transferMoney(String sourceName, String targetName, float money);
}
public class Account {
private int id;
private String name;
private float money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String name) {
try{
return runner.query(connectionUtils.getConnection(),"select * from account where name = ? ",new BeanHandler<Account>(Account.class),name);
}catch (Exception e){
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccountId(int id) {
try{
runner.update(connectionUtils.getConnection(),"delete from account where id=?",id);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class AccountServiceImpl implements IAccountService {
private AccountDaoImpl accountDaoImpl;
public void setAccountDaoImpl(AccountDaoImpl accountDaoImpl) {
this.accountDaoImpl = accountDaoImpl;
}
@Override
public void saveAccount(Account account) {
accountDaoImpl.saveAccount(account);
}
@Override
public Account findAccountByName(String name) {
return accountDaoImpl.findAccountByName(name);
}
@Override
public void updateAccount(Account account) {
accountDaoImpl.updateAccount(account);
}
@Override
public void deleteAccountId(int id) {
accountDaoImpl.deleteAccountId(id);
}
@Override
public void transferMoney(String sourceName, String targetName, float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDaoImpl.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDaoImpl.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDaoImpl.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDaoImpl.updateAccount(target);
}
}
我们在AccountServiceImpl中如果调用transferMoney的话因为有int i=1/0 的异常,就会出现转账人的额钱少了,而收账人的钱却并没有多。这是因为在转账的过程中会有很多次与数据库的交互操作,每个都会有一个独立的连接,一个执行完数据库中就会产生相应的变化。我们要实现的就是成功的话都成功,不成功过的话都不成功。要实现这个我们起码要保证这些操作的都有一个相同的连接,下面就是我们实现的一个数据库连接工具类:
public class ConnectionUtils {
ThreadLocal<Connection> connectionThreadLocal =new ThreadLocal<Connection>();
DataSource dataSource;
public ConnectionUtils(DataSource dataSource){
this.dataSource =dataSource;
}
// 这里的方法是用来获取连接
public Connection getConnection( ){
try{
Connection connection = connectionThreadLocal.get();
// 我们需要先判断connectionThreadLocal中是否含有连接
if(connection==null){
connection = dataSource.getConnection();
connectionThreadLocal.set(connection);
}
return connection;
}catch (Exception e){
throw new RuntimeException(e);
}
}
// 用于将线程与连接解绑
public void removeConnection(){
connectionThreadLocal.remove();
}
}
上边是确保一个事务中只有一个连接,在这个类中removeConnection方法也有很重要的用途:设想如果我们的连接关闭了,但是还是存在于connectionTheadLocal中,那就相当于是一个废连接,既不能有效的连接数据库,但是检测的时候又会显示有连接存在而妨碍了新链接的创建,我们可以通过remove方法保证连接关闭后将这个废弃连接与线程解绑,从而避免上述的废连接问题。
public class ConnectionMange {
ConnectionUtils connectionUtils;
public ConnectionMange(ConnectionUtils connectionUtils){
this.connectionUtils =connectionUtils;
}
// 开启事务的方法
public void begainMatter(){
try{
connectionUtils.getConnection().setAutoCommit(false);
}catch (Exception e){
throw new RuntimeException(e);
}
}
// 事务提交的方法
public void commitMatter(){
try{
connectionUtils.getConnection().commit();
}catch (Exception e){
throw new RuntimeException(e);
}
}
// 事务异常回滚的方法
public void rollBackMatter(){
try{
connectionUtils.getConnection().rollback();
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void release(){
try{
connectionUtils.getConnection().close();
connectionUtils.removeConnection();
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
我们将事务管理引入进来,在这里我们展示的仅仅是transferMoney方法:
public class AccountServiceImplTx {
private AccountDaoImpl accountDaoImpl;
public void setAccountDaoImpl(AccountDaoImpl accountDaoImpl) {
this.accountDaoImpl = accountDaoImpl;
}
private ConnectionMange connectionMange;
public void setConnectionMange(ConnectionMange connectionMange){
this.connectionMange =connectionMange;
}
public void transferMoney(String sourceName, String targetName, float money) {
try{
connectionMange.begainMatter();
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDaoImpl.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDaoImpl.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDaoImpl.updateAccount(source);
// int i=1/0;
//2.6更新转入账户
accountDaoImpl.updateAccount(target);
connectionMange.commitMatter();
}catch (Exception e){
connectionMange.rollBackMatter();
throw new RuntimeException(e);
}finally {
connectionMange.release();
}
}
}
最后就是我们的beans.xml配置文件了:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Service层的配置-->
<bean id="accountService" class="shiwuguanli.jabc.AccountServiceImpl">
<property name="accountDaoImpl" ref="accountDao"></property>
</bean>
<!--Dao层的配置-->
<bean id="accountDao" class="shiwuguanli.jabc.AccountDaoImpl">
<property name="connectionUtils" ref="connectionUtils"></property>
<property name="runner" ref="runner"></property>
</bean>
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner"></bean>
<bean id="connectionUtils" class="shiwuguanli.jabc.ConnectionUtils">
<constructor-arg ref="dateSource"></constructor-arg>
</bean>
<!--数据源的配置-->
<bean id="dateSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://47.106.156.8:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="Lml@18862283081"></property>
</bean>
<bean id="connectionMange1" class="shiwuguanli.jabc.ConnectionMange">
<constructor-arg ref="connectionUtils"></constructor-arg>
</bean>
<!--这里配置的是事务管理-->
<bean id="accountServiceImplTx" class="shiwuguanli.jabc.AccountServiceImplTx">
<property name="accountDaoImpl" ref="accountDao"></property>
<property name="connectionMange" ref="connectionMange1"></property>
</bean>
</beans>
-
事务管理的代理实现方式
上面我们是只实现了对一个方法的事务管理呢,如果有很多的方法,那我们关于事务管理的这一部分就会出现很大的代码冗余,解决这个冗余的方法之一便是通过代理来实现。我们来创建一个BeanFactory,通过这个工厂类来产生对service层进行代理的对象,我们这里bean的创建是通过普通的方式实现的,说以一会bean实例配置的时候就业会与原来的有所不同:
public class BeanFactory {
private IAccountService accountService;
private ConnectionMange connectionMange;
public void setAccountService(IAccountService accountservice) {
this.accountService = accountService;
}
public void setConnectionMange(ConnectionMange connectionMange) {
this.connectionMange = connectionMange;
}
public IAccountService getAccountServiceProxy(){
return (AccountServiceImpl) Proxy.newProxyInstance(AccountServiceImpl.class.getClassLoader(),
AccountServiceImpl.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object =null;
try{
connectionMange.begainMatter();
object = method.invoke(accountService,args);
connectionMange.commitMatter();
return object;
}catch (Exception e){
connectionMange.rollBackMatter();
throw new RuntimeException(e);
}finally {
connectionMange.release();
}
}
});
}
}
xml配置文件新增内容:
<bean id="beanFactory" class="shiwuguanli.jabc.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="connectionMange " ref="connectionMange"></property>
</bean>
<bean id="serviceProxy" factory-bean="beanFactory" factory-method="getAccountServiceProxy"></bean>
-
Java代理
在java中代理的运用我们可以类比于生活中的中介(卖家与买家通过中介来认识沟通)、明星经纪人(粉丝与明星之间一般是通过经纪人来预约联系,这样可以避免大量的分析直接联系给明星带来困扰,经过经纪人“审核”的粉丝可以与明星进行沟通互动)之类的第三方,这个第三方可以起到安全屏障、减少双方之间的关联程度。在java代理的理念之上诞生了经典模式之一的代理模式又称为委托模式,通过他可以有效的实现软件工程中所提出的高内聚低耦合的特征,从而提升代码的质量。代理又可以分为静态代理和动态代理,各有优缺点。
-
静态代理
静态代理中代理对象持有目标对象的语柄(相当于是目标对象的一个实现,在代理对象中充当一个成员变量),在代理对象中可以通过句柄来调用目标对象中的具体方法,从此对所有目标对象的调用都是通过调用代理对象实现的。静态代理的好处在于代码的理解简单,但是这也造成了当目标对象中有较多的方法时代理对象中也要编写实现相对应的方法,这就业造成了代码的繁琐。
这里创建的是一个接口,规定了需要实现的具体方法,目标对象与代理对象都需要实现它。(个人理解:如果省略掉这个接口,那么就失去了代理的实际意义。因为这时候就相当于是带一个对象,对象中中定义了另一个对象的成员变量,并且这个充当语柄的变量是固定的,这只能是一个固定的类。而通过接口的形式定义的话,那么所有实现了这个接口的类,都可以通过新建对象来实现这个语柄从而通过这个代理对象来实现代理)
package javaagency;
public interface Subject {
public void request();
}
这里是定义的是一个目标对象,实现了规定的接口:
package javaagency;
public class SubjectImpl implements Subject{
@Override
public void request() {
System.out.println("I am dealing the request");
}
}
这里定义的是一个代理类,通过实现定义的接口,从而可以代理所有实现了定义接口的对象,成员subject就是句柄:
package javaagency;
import sun.awt.geom.AreaOp;
public class StaticProxy implements Subject{
// 句柄,通过他来实现代理同时实现对目标对象的操作
private Subject subject;
public StaticProxy(Subject subject){
this.subject = subject;
}
// 如果函数多的话就会表现出对函数包装的繁琐性
@Override
public void request() {
System.out.println("这里展示的是一个前置的处理");
subject.request();
System.out.println("通过这里展示的是一个后置的处理");
}
}
这里是一个主类,目标对象通过成员变量的方式放在代理对象的构造方法中,类构造一个代理对象,来实现相应的操作:
package javaagency;
public class StaticProxyDemo {
public static void main(String args[]){
// 创建实际的对象
SubjectImpl subject=new SubjectImpl();
// 把实际对象封装到代理对象中
StaticProxy p=new StaticProxy(subject);
p.request();
}
}
-
单接口动态代理
动态代理中,对目标对象的方法每次调用都会进行拦截,送到道理处理器来进行相应的处理。代理处理器中是有目标对象的句柄,代理处理器通过实现InvocationHandler接口的invoke方法来处理目标对象的调用即所有的代理对象的方法的调用都会转发到invoke中,在invoke内部可以根据method参数来实现目标对象不同的方法来响应不同的请求。
动态代理实现的方式有两种,一种是基于子类的,需要依赖第三方的库;还有一种就是我们现在在说的基于接口的,现在我们先定义一个接口:
public interface ICelebrity {
public void helloIntroduce(String name);
public void doMeet( );
}
在我们基于接口的代理中需要实现这个接口的是,我们的目标对象:
public class CelebrityTom implements ICelebrity{
@Override
public void helloIntroduce(String name) {
System.out.println(name+"你好,"+"我是超级大明星,Tom");
}
@Override
public void doMeet() {
System.out.println("我是Tom,很高兴见到你");
}
}
不同于我们的静态代理,如果想要实现动态代理首先我们需要一个负责具体处理事务的代理处理器以及一个代理对象,这个处理器需要我们传递进去一个目标对象,这个目标对象是通过构造函数来传递。通过invoke()方法来拦截外部对我们所有的调用,再经过他来给我们具体的处理:
public class CelebrityHander implements InvocationHandler {
private CelebrityTom celebrityTom;
public CelebrityHander(CelebrityTom celebrityTom){
this.celebrityTom=celebrityTom;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里边通过判断是那一个方法,从而实现针对不同方法的不同处理
if("helloIntroduce"==method.getName()){
System.out.println("你好,我是Tom的经纪人,接下来Tom给你打招呼");
Object object =method.invoke(celebrityTom,args);
return object;
}else if("doMeet"==method.getName()){
if ( System.currentTimeMillis()%2==1){
System.out.println("我是Tom的经纪人,Tom现在有时间与你见面");
Object object =method.invoke(celebrityTom);
return object;
}else {
System.out.println("我是Tom的经纪人,Tom现在没有时间给你见面");
return null;
}
}else {
System.out.println("你的方法调用错误!!!!!!!!!");
return null;
}
}
}
现在我们已经有了代理处理器,如果我们要实现代理的话还少一个代理对象,我们的这个代理处理器对象是通过Proxy.newProxyInstance()方法来生成的,这个方法需要传递三个参数:目标类的来加载器、目标类的接口集、目标类的处理器对象
public class TestMain {
public static void main(String[] args) {
CelebrityTom celebrityTom =new CelebrityTom();
CelebrityHander celebrityHander =new CelebrityHander(celebrityTom);
ICelebrity celebrityProxy =(ICelebrity)Proxy.newProxyInstance(CelebrityTom.class.getClassLoader(),
CelebrityTom.class.getInterfaces(),celebrityHander);
celebrityProxy.helloIntroduce("柳梦磊");
celebrityProxy.doMeet();
}
}
-
多接口动态代理
多接口动态代理这里必须是亮点,当初在这里迷了很长时间,网上没有很好的实现案例,网课的老师说这不是推荐的代理,所以也就没有真的实现!但是本人本着弄不清楚心理不舒服的心态,钻牛角尖,哈哈经过反复的尝试(主要是笨),总算是柳暗花明又一村,心理舒服多了!应该是这样实现的,代码实现了在多接口的时候,同名方法谁靠前调用谁,不是同名的方法则正常调用。但是代码在判断具体访问是哪个目标对象的时候实现的不理想,是通过将meath.toString()转化为字符串,再通过indexOf()方法来判断字符串中是否含有类名,感觉这样实现起来不太优雅。理想中的优雅实现是直接通过meath获得访问对象名在做判断,但是水平有限,没有找到合适的实现方法,望各路英豪赐教!!!
与接口代理相同,先准备目标对象的接口以及目标对象,只不过我们多接口动态代理中是至少有两个目标对象接口以及目标对象:
public interface ICelebrityJoy {
void helloIntroduce(String name);
void doMeet();
void identityJoy();
}
public interface ICelebrityTom {
void helloIntroduce(String name);
void doMeet();
void identityTom();
}
public class CelebrityJoy implements ICelebrityJoy {
@Override
public void helloIntroduce(String name) {
System.out.println(name+"你好,"+"我是超级大明星,Joy");
}
@Override
public void doMeet() {
System.out.println("我是Joy,很高兴见到你");
}
@Override
public void identityJoy(){
System.out.println("@@@@@@这里是Joy的身份鉴定方法@@@@@@@");
}
}
public class CelebrityTom implements ICelebrityTom {
@Override
public void helloIntroduce(String name) {
System.out.println(name+"你好,"+"我是超级大明星,Tom");
}
@Override
public void doMeet() {
System.out.println("我是Tom,很高兴见到你");
}
@Override
public void identityTom(){
System.out.println("@@@@@@这里是Tom的身份鉴定方法@@@@@@@");
}
}
虽然我们有两个的目标对象但是我们只需要一个代理处理器,在代理处理器的构造方法中对两个私有的目标对象实例化,在多接口访问的时候通过判定之后给invoke方法指定具体的目标对象:
public class CelebrityHander implements InvocationHandler {
// 通过构造函数实例化两个目标对象
private CelebrityTom celebrityTom;
private CelebrityJoy celebrityJoy;
public CelebrityHander(CelebrityTom celebrityTom, CelebrityJoy celebrityJoy){
this.celebrityTom =celebrityTom;
this.celebrityJoy =celebrityJoy;
}
// 拦截所有访问目标对象的方法,再统一处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过判断当前访问的目标对象类型,来传入合适的目标对象句柄,通过目标对象句柄,来实现对具体目标
// 对象的访问,这里主要是将通过判断语句来实现对具体方法的具体处理,这里的方法可能有冗余这里可以对
// 判断语句部分做进一步抽取
if (method.toString().indexOf("ICelebrityJoy") != -1)
{
if ("helloIntroduce" == method.getName())
{
System.out.println("你好,我是JoyJoy的经纪人,接下来Tom给你打招呼");
Object object = method.invoke(celebrityJoy, args);
return object;
} else if ("doMeet" == method.getName())
{
if (System.currentTimeMillis() % 2 == 1)
{
System.out.println("我是Joy的经纪人,Joy现在有时间与你见面");
Object object = method.invoke(celebrityJoy);
return object;
}
else
{
System.out.println("我是Joy的经纪人,Joy现在没有时间给你见面");
return null;
}
}
else if ("identityJoy" == method.getName())
{
Object object = method.invoke(celebrityJoy, args);
return object;
}
else {
System.out.println("方法调用异常");
}
}
else if (method.toString().indexOf("ICelebrityTom") != -1)
{
if ("helloIntroduce" == method.getName())
{
System.out.println("你好,我是Tom的经纪人,接下来Tom给你打招呼");
Object object = method.invoke(celebrityTom, args);
return object;
}
else if ("doMeet" == method.getName())
{
if (System.currentTimeMillis() % 2 == 1)
{
System.out.println("我是Tom的经纪人,Tom现在有时间与你见面");
Object object = method.invoke(celebrityTom);
return object;
} else {
System.out.println("我是Tom的经纪人,Tom现在没有时间给你见面");
return null;
}
} else if ("identityTom" == method.getName()) {
Object object = method.invoke(celebrityTom, args);
return object;
} else {
System.out.println("方法调用异常");
return null;
}
}
else {
return null;
}
return null;
}
}
这里我们的这个代理处理器看起来比较复杂,主要是对目标对象,以及目标对象方法的一个判断,在判断之后做出相应的处理。相比于单接口的动态代理变化的地方在于,构造函数中的目标对象添加了一个,以及在方法的判断体之前建了一个关于目标对象的判断体:if (method.toString().indexOf("ICelebrityJoy") != -1)
接线来就是我们的的主类了,我们的代理对象就是在这里创见的,与单接口代理的的差别之初就是这里的代理对象创建分成了多步:
public class TestMain {
public static void main(String[] args) throws Exception {
// 生成代理处理器,代理处理器中存在两个目标类型
CelebrityTom celebrityTom =new CelebrityTom();
CelebrityJoy celebrityJoy =new CelebrityJoy();
CelebrityHander celebrityHander =new CelebrityHander(celebrityTom,celebrityJoy);
// 生成本类的类加载器
ClassLoader classLoader =TestMain.class.getClassLoader();
// 分两步生成代理对象
Class<?> proxyClass = Proxy.getProxyClass(classLoader,new Class<?>[]{ICelebrityTom.class,ICelebrityJoy.class});
Object proxy = proxyClass.getConstructor(new Class[]{InvocationHandler.class}).
newInstance(new Object[]{celebrityHander});
// 用代理对象来访问目标对象,如果接口中有同名方法,则调用靠前的接口中的方法
ICelebrityJoy celebrityJoy1 = (ICelebrityJoy) proxy;
celebrityJoy1.helloIntroduce("柳梦磊");
celebrityJoy1.identityJoy();
ICelebrityTom celebrityTom1 = (ICelebrityTom) proxy;
celebrityTom1.helloIntroduce("柳梦磊");
celebrityTom1.identityTom();
}
}
-
基于父类的动态代理
基于父类的代理我们是通过一个第三方库cglib实现的,他要求目标对象不能是最终类,也就是说目标对象的类不能被final修饰,这里也很好理解,如果被final修饰那么子类就无从谈起,那么的们的实现也就变得不可能。在开始之前我们先配置依赖,创建一个目标对象:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
public class CelebrityTom {
public void helloIntroduce(String name) {
System.out.println(name+"你好,"+"我是超级大明星,Tom");
}
public void doMeet() {
System.out.println("我是Tom,很高兴见到你");
}
}
在这里我们的目标对象是通过Enhancer类的creat方法来实现的,在这里用到的一些方法参数和我们基于接口实现动态代理(Proxy.newProxyInstance)用到的是很像的。
Enhancer.creat()方法对应的源码是:public static Object create(Class type, Callback callback)
Proxy.newProxyInstance方法的源码是:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
在这里我们可以清楚的看到,他们的主要差别就是creat的缺少Class<?>[] interfaces,其他的两个参数虽然看似形式不同但是,作用目的是相同的,获取目标对象的信息(类加载器 或 class文件)、代理的具体处理。在实现对调用目标对象方法的拦截上分别使用的是invoke()方法、和intercept()方法,这个方法的差别在于intercept()方法多出第四个参数——本方法的代理对象:
public Object invoke(Object proxy, Method method, Object[] args)
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
从上面的代码中我们也可以清楚的看到我们实现的是Callback子接口的实现类,来对目标对象进行加强,invoke()方法、和intercept()方法的内部实际是相同的。
public class TestMain {
public static void main(String[] args) {
final CelebrityTom celebrityTom =new CelebrityTom();
CelebrityTom cglibProxyTom = (CelebrityTom) Enhancer.create(celebrityTom.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
System.out.println(methodProxy);
if("helloIntroduce"==method.getName()){
System.out.println("你好,我是Tom的经纪人,接下来Tom给你打招呼");
Object object =method.invoke(celebrityTom,args);
return object;
}else if("doMeet"==method.getName()){
if ( System.currentTimeMillis()%2==1){
System.out.println("我是Tom的经纪人,Tom现在有时间与你见面");
Object object =method.invoke(celebrityTom);
return object;
}else {
System.out.println("我是Tom的经纪人,Tom现在没有时间给你见面");
return null;
}
}else {
System.out.println("你的方法调用错误!!!!!!!!!");
return null;
}
}
});
cglibProxyTom.doMeet();
}
}
-
Spring AOP编程
AOP编程是一种新的编程思想,同时他也是Spring框架的核心概念之一,他将通用的功能需求从众多的类中分离出来,使得很多类共享一个行为,一旦发生变化,不需要修改很多的类只需要修改这个行为即可,着这里个人认为这个行为是通过配置文件的形式来表现出来的。AOP编程解决了代码的耦合程度,使得业务逻辑的变化不需要修改源代码以及重启项目,同时加快了编程和测试的速度。
AOP编程和数据库的触发器有些类似,如果要是配置文件发生了什么变化的话那么源代码的运行就会发生相应的变化。与OOP面向对象编程相比较,OOP更像是对纵轴的管理主要管理一类的,而AOP更像是横轴的管理他管理的是具有共同行为的不同方法,也可以说是管理不同类中有相互关联的一些方法
-
基于配置文件实现的Spring AOP
准备一个一会我们要使用的对象,这个对象可疑类似于代理中的目标对象,一会我们将通过Spring AOP的方式对这个方法进行加强。
public interface IAccountService {
void saveAccount();
void updateAccount(int i);
int deleteAccount();
}
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
这是我们又来对AccountServiceImpl对象进行加强的一个对象类型:
public class Logger {
// 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
public void printLog(){
System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
}
}
准备我们的XML配置文件,在这里我们需要加入aop的一个名称空间,通过标签配置需要使用到的一些Bean。通过<aop:config>标签来开始我们的aop相关的一些配置,<aop:aspect>标签我们既可以配置在<aop:config>内,这时候这个切面是可以被所有的<aop:aspect>通过id引用的,也可以配置到单独的<aop:aspect>中,甚至可以通过属性的方式直接配置到具体的处理方法中去。有关切面配置中的execution表达式,表达式的常规写法是:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
例如:public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
在这里面我们可以采用同配置的简写方式,访问修饰符可以省略,返回值可以使用*代替,里面的包名也可以使用*代替,当有多个包名时我们可以用*..来代表当前包下的所有子包,类名以及方法名也是可以使用*代替的,参数的我们可以使用..代表任意参数或无参数,当我们不适用..,一般类型的参数我们直接使用类型名例如:int,当参数是我们自定义类型的时候应该写成包名加类名的全类名形式。所以说全通配的写法就是: * *..*.*(..),在我们的实例代码中如果我们想要匹配业务层实现类下所有的方法时: * com.itheima.service.impl.*.*(..)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<bean id="accountServiceTest" class="com.itheima.service.impl.AccountServiceTest"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:pointcut id="pointcut1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut-ref="pointcut1"></aop:before>
</aop:aspect>
</aop:config>
</beans>
测试类:
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}
以上关于AOP方面的我们主要告诉程序我们针对那些方法做切面编程(服务层实现类的所有方法)、我们对切面的方法做一个怎样的加强(在<aop:aspect>中我们指定了我们在加强中用到的方法在那个Bean中,<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联--><aop:before method="printLog" pointcut-ref="pointcut1"></aop:before>)。就我们这里例子我们的切面对应的方法实际上可以看做是目标对象的方法,这里所说的通知方法也就是我们又来加强目标对象的方法。
-
原生Java的实现
我们的整个实现是基于动态代理来实现的,在程序的入口位置通过initXML()方法来对文件的配置文件,以及对配置文件的监听来做一个初始化。配置文件的中我们可以按一定的格式写入我们的信息,XML解析器将读取到的信息放置到我们的代理对象中,代理对象的拦截方法invoke()来根据这些信息对目标的对想的方法进行处理。监听中最主要是通过WatchService这个工具类来实现的,监听进程相当于是一个后台进程,当我们配置文件中内容发生改变并保存时,监听就会告知程序,那么既然配置信息发生了变化,那么程序的运行也就会发生变化,而程序并不需要重启重编译!
我是通过配置文件来实现Person中三个方法的先后运行顺序,相比于代理,我们可以把eat()看成是目标对象的方法,washHand()、bath()是代理对象中用于处理的方法。亦或是说eat()是切入点,washHand()、bath()是通知(增强)。
public interface Person {
void eat();
void washHand();
void bath();
}
public class PersonImpl implements Person {
@Override
public void eat() {
System.out.println("I am eating");
}
@Override
public void washHand() {
System.out.println("I am washing hands");
}
@Override
public void bath() {
System.out.println("I am bathing");
}
}
ProxyHandler类,在这里我们的程序是有冗余的如果要想更好的更接近的实现一个类似于Spring AOP的功能,我们应该对这个类中的大部分的内容做一个抽取,例如静态实例,以及目标对象(切入点)、处理目标对象的一个增强对象(通知),在这里指的都是我们的Person。讲这些可以东西通过父子类集成的关系,继承过来。
public class ProxyHandler implements InvocationHandler{
// 这两个成员变量是可以直接通过类名进行赋值的,在XmlReader中我们将在配置文件中得到的配置信息
// 赋值到这里,代理处理器使用;如果有多个代理处理器的话我们这里就会显得冗余,可以通过抽取父类
// 通过继承来实现
static String beforeMethod = "";
static String afterMethod = "";
// 通过构造函数实例化一个目标对象,在这里我们的Person即是目标对象又是处理对象
private Person receiverObject;
public ProxyHandler(Person object){
this.receiverObject = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//处理before方法
if(beforeMethod!=null&&beforeMethod.length()>0){
ClassLoader cl = ProxyHandler.class.getClassLoader();
Class<?> c = cl.loadClass(receiverObject.getClass().getName());
Method m=c.getMethod(beforeMethod);
Object obj = c.newInstance();
m.invoke(obj);
}
//处理目标方法
Object result = method.invoke(receiverObject, args);
//处理after方法
if(afterMethod!=null&&afterMethod.length()>0){
method.invoke(receiverObject, args);
ClassLoader cl = ProxyHandler.class.getClassLoader();
Class<?> c = cl.loadClass(receiverObject.getClass().getName());
Method m=c.getMethod(afterMethod);
Object obj = c.newInstance();
m.invoke(obj);
}
return result;
}
}
测试主类:
public class Main {
public static void initXml(){
// 创建一个解析我们XML文件的对象
XmlReader.readXml("aops.xml");
// 创建一个对我们配置文件进行监听的对象
ResourceListener.addListener("C:\\Users\\柳梦磊\\Desktop\\PMOOC04-03\\");
}
public static void main(String[] args) throws Exception{
Main.initXml();
// 创建代理对象的步骤
Person action = new PersonImpl();
ProxyHandler mh = new ProxyHandler(action);
ClassLoader cl = Main.class.getClassLoader();
Class<?> proxyClass = Proxy.getProxyClass(cl, new Class<?>[]{Person.class});
Person proxy = (Person) proxyClass.getConstructor(new Class[]{InvocationHandler.class}).
newInstance(new Object[]{mh});
// 执行一个循环体,使得程序不间断执行,但配置文件放生改动的时候执行程序就会发生相应的改动
// 而不许需要重新启动、编译程序
while(true)
{
proxy.eat();
try{
Thread.sleep(3000);
}
catch(Exception e){
e.printStackTrace();
}
}
}
}
起到对配置文件的一个解析作用,将解析得到的信息传送到了代理处理器:
public class XmlReader {
public static void readXml(String filePath){
String xml = load(filePath);
try{
// DocumentBuilderFactory是一个抽象方法所以不能直接创建,我们可以通过newInstance()方法
// 来返回一个创建XML解析器的工厂
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
// 通过工厂得到一个XML解析器对象
DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
// 读取配置文件,将要解析的XML文件转化为流,便于后续的处理
StringReader sr = new StringReader(xml);
InputSource is = new InputSource(sr);
// 利用解析器的parse()方法来解析XML文件得到一个代表整个文档的document对象,根据Document
// 的性质来做下一步的处理
Document document = dbBuilder.parse(is);
// 获取文档的根节点
Element root = document.getDocumentElement();
System.out.println(root);
// getChildNodes()方法获得子节点
findMethod(root.getChildNodes());
}
catch(Exception e){
e.printStackTrace();
}
}
public static void findMethod(NodeList elementList){
for (int i = 0; i < elementList.getLength(); i++) {
Node elementNode = elementList.item(i);
System.out.println("allnode: "+elementNode.getNodeName());
// 通过getNodeType() == Node.ELEMENT_NODE来判断当前的节点是否是元素节点,是的话则进入
if (elementNode.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) elementNode;
String name = element.getNodeName();
// 判断当前的元素节点是否是aop,在我们编写的解析中把aop当做是一个切面,在这个切面
// 中我们规定方法的执行规则,如果是的话则进入readMethod()方法来读取详细的规定,如
// 果不是的话则递归继续查找
if(name.equalsIgnoreCase("aop")){
readMethod(element.getChildNodes());
}
else{
findMethod(element.getChildNodes());
}
}
}
}
private static void readMethod(NodeList elementList){
String methodName = "";
// 在这里我们通过上一个方法传递过来的参数element.getChildNodes()拿到了<aop>节点内的元素
// 这个方法便是对我们切面逻辑的一个解析处理,将读取到的信息存储在代理处理器上,在我们的这
// 个程序上就是ProxyHandler,在这里我们把处理器中相同的内容抽象到ProxyDfinet中,其他的代理
// 处理器通过继承ProxyDfinet来获得相应的能力
for (int i = 0; i < elementList.getLength(); i++) {
Node elementNode = elementList.item(i);
if (elementNode.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) elementNode;
String name = element.getNodeName();
if(name.equals("method")){
if(methodName==null||methodName.length()==0) {
methodName = element.getFirstChild().getTextContent();
}
}
else if(name.equals("type")){
String type = element.getFirstChild().getTextContent();
if(type.equals("after")){
ProxyHandler.afterMethod = methodName;
}
else{
ProxyHandler.beforeMethod = methodName;
}
}
}
}
}
private static String load(String path){
try{
File file = new File(path);
FileReader reader = new FileReader(file);
BufferedReader bReader = new BufferedReader(reader);
StringBuilder sb = new StringBuilder();
String s = "";
while ((s =bReader.readLine()) != null) {
sb.append(s + "\n");
//System.out.println(s);
}
bReader.close();
return sb.toString();
}
catch(Exception e){
e.printStackTrace();
}
return null;
}
}
这里是 一个监听的方法,借助于WatchService工具类实现:
public class ResourceListener {
// 创建一个缓冲线程池
private static ExecutorService fixedThreadPool=Executors.newCachedThreadPool();
private WatchService ws;
private String listenerPath;
private ResourceListener(String path){
try
{
ws=FileSystems.getDefault().newWatchService();
this.listenerPath=path;
}
catch (Exception e)
{
e.printStackTrace();
}
}
private void start(){
fixedThreadPool.execute(new Listener(ws, listenerPath));
}
// 添加监听事件通过start()方法开始监听
public static void addListener(String path){
try{
ResourceListener resourceListener=new ResourceListener(path);
Path p=Paths.get(path);
p.register(resourceListener.ws, StandardWatchEventKinds.ENTRY_MODIFY,StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_CREATE);
resourceListener.start();
}
catch (Exception e){
e.printStackTrace();
}
}
// 内部类Listener实现对我们配置文件的持续监听
class Listener implements Runnable{
WatchService ws;
String listenerPath;
Listener(WatchService ws,String listenerPath){
this.ws = ws;
this.listenerPath = listenerPath;
}
@Override
public void run() {
try {
while(true){
WatchKey watchKey = ws.take();
List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
for(WatchEvent<?> event : watchEvents){
String context = event.context().toString();//对象
String kind = event.kind().toString();//变更类型
if(context.equals("aops.xml")){
if(kind.equals("ENTRY_MODIFY")){
XmlReader.readXml(listenerPath+"/"+event.context());
}
}
}
watchKey.reset();
}
} catch (InterruptedException e) {
e.printStackTrace();
try {
ws.close();
} catch (Exception e1) {
}
}
}
}
}
-
AOP基于注解的事务管理、切面编程: