Access control for partial exposure

Gain high-speed, fine-grained access control for Java fields and methods

Java's built-in access control won't let you expose fields and methods (members) to some classes in a package, while hiding them from other classes in that same package. In this article, I'll show you the Access Control design pattern, which lets you realize such partial visibility. When developing a server class, you may choose to offer any other class (the client) access to certain private server members, while other private members remain hidden. Such selective access may be offered to a client regardless of the package in which it resides, and is visible only to a client that acquires the privileged access.

Essentially, the Access Control pattern can make a non-inner class (Client) behave exactly like an inner class of Server. On the other hand, this virtual inner class differs from a true inner class in that you can design it to see only some Server members, but not others (a true inner class of Server can always see everything inside of Server).

The Access Control pattern used in this article accomplishes the following:

  • Increases the flexibility and extensibility of designs that employ inner classes
  • Decreases the size and complexity of classes that employ many inner classes
  • Provides selective access to sensitive private members

Java's built-in access control

Consider the following classes:

public class Server {
    void serverMethod() {
    }
}
public class Client {
    void clientMethod() {
        serverMethod();
    }
}

Using Java's built-in access control, you may declare serverMethod() public, protected, friendly (absent access specifier), or private. The clientMethod() has the following corresponding degrees of access:

  • public void serverMethod()...: The clientMethod() compiles regardless of which package Client resides in, and regardless of whether Client is a Server subclass.
  • protected void serverMethod()...: The clientMethod() compiles only if Client resides in the same package as Server, or if Client extends Server.
  • void serverMethod()...: The clientMethod() compiles only if Client resides in the same package as Server.
  • private void serverMethod()...: The clientMethod() won't compile; only code residing within the Server class can access serverMethod().

The need for fine-grained access control

There are several situations in which you might want to grant member access only to certain other classes.

Suppose a given class needs many inner classes. You can define any number of inner classes within a class, but that makes for large class files that are difficult to read and maintain. I've encountered this situation with graphics programming, where editable shapes accumulate a dizzying variety of inner classes for responding to a corresponding variety of editing events (usually transmitted to the shape in the form of mouse and/or key events). It may be convenient to turn each inner class into a regular class (defined in its own file), and mimic the inner class behavior by granting access to the appropriate private fields and methods of the original outer class. In this case, you want to grant such access only to those classes intended to behave like the original outer class's inner classes.

Here's another situation calling for fine-grained access control: You develop a Connection object that encapsulates a connection to a database or some other resource accessed through a network. Since the database or network connection must be initialized when the Connection object is instantiated, creating a Connection object may be expensive. So that clients don't have to wait for Connection objects to instantiate, you may develop a ConnectionPool class that pools the Connection instances. Suppose a client acquires a Connection instance from the pool, and then asks the Connection to access its encapsulated resource. The Connection may discover that the network is down, the database is unavailable, or the resource is inaccessible for another reason. In that case, you may want the Connection to shut down or reinitialize the pool from which it came. (Those pooled connections are no longer useful to clients anyway.)

The pool can then immediately inform future clients that the resource is unavailable. Otherwise, a client cannot discover the connection problem until it acquires a Connection object, and then waits for a call to the appropriate Connection method to time out and throw an exception. To accommodate this functionality, you provide the ConnectionPool class with a resourceUnavailable() method. That method is called by a Connection object to notify ConnectionPool that the resource is unavailable and allow the pool to take the appropriate action. But who should have access to the resourceUnavailable() method? Should any class residing in the same package as ConnectionPool be able to call resourceUnavailable()? If so, you must be familiar with the entire package's design in order to use resourceUnavailable() with confidence.

One solution to this problem makes resourceUnavailable() a private method and Connection a ConnectionPool inner class:

public class ConnectionPool {
    private resourceUnavailable() {
    }
    public class Connection {
    }
}

This design exposes the resourceUnavailable() method to instances of Connection and ConnectionPool, but hides the method from every other class. There are two problems with this approach. First, Connection can access not only resourceUnavailable(), but also all ConnectionPool's private fields and methods. Second, to subclass Connection, you must also subclass ConnectionPool and make the Connection subclass an inner class of that subclass. This makes developing the two classes along diverging paths difficult. For example, suppose you want two kinds of specialized pools -- ConnectionPoolSubclassA and ConnectionPoolSubclassB -- and two kinds of specialized connections -- ConnectionSubclassA and ConnectionSubclassB. If you want to mix and match these specializations, you're stuck with the following mess:

public class ConnectionPoolIntermediateClass extends ConnectionPool {
    public class ConnectionA extends Connection {
    }
    public class ConnectionB extends Connection {
    }
}
public class ConnectionPoolSubclassA extends ConnectionPoolIntermediateClass {
    // Inside this class, we can instantiate either Connection, ConnectionA, or
ConnectionB.
}
public class ConnectionPoolSubclassB extends ConnectionPoolIntermediateClass {
    // Inside this class, we can instantiate either Connection, ConnectionA, or
ConnectionB.
}

The problem described above is really just a manifestation of a deeper problem -- "Connection" and "ConnectionPool" are almost completely unrelated concepts, and should therefore be expressed as separate classes. The only thing these classes have in common is the need to call resourceUnavailable(). However, the above design links the destiny of the two classes so that you cannot extend Connection without also extending ConnectionPool.

The need for speed

This article's subtitle advertises high-speed access control. So why is speed important anyway? Java's built-in access control is enforced at compile time, so there's no performance penalty for using it. You might think twice about using fine-grained access control if an accompanying performance penalty existed. Besides, fast is always better than slow.

Access Control design pattern

The Access Control pattern, in its most basic form, is presented below:

// ******** VirtualMethod ********
public abstract class VirtualMethod {
    public abstract void call();
}
// ******** Server ********
public class Server {
    
    private void shakeRattleAndRoll() {
        System.out.println("SHAKE ... RATTLE AN' ROLL!!!");
    }
    private class ShakeRattleAndRoll extends VirtualMethod {
        public void call() {
             shakeRattleAndRoll();
        }
    }
    public Client getClient() {
        return new Client(new ShakeRattleAndRoll());
    }
  
}
// ******** Client ********
public class Client {
    
    private VirtualMethod shakeRattleAndRoll;
    
    public Client(VirtualMethod shakeRattleAndRoll) {
        this.shakeRattleAndRoll = shakeRattleAndRoll;
    }
    
    public void demonstrateAccess() {
        shakeRattleAndRoll.call();
    }
    
}
// ******** Test ********
public class Test {
    public static void main(String[] args) {
        Server server = new Server();
        Client client = server.getClient();
        client.demonstrateAccess();           // Prints out:  SHAKE ... RATTLE AN' ROLL!!!
    }
}

Because VirtualMethod is a public class, anyone can declare a handle of type VirtualMethod and call the call() method on it. Therefore, anyone can call the call() method ShakeRattleAndRoll uses to access Server's private shakeRattleAndRoll() method. On the other hand, the ShakeRattleAndRoll class provides the only VirtualMethod implementation that can actually see inside Server.

Because ShakeRattleAndRoll is a private inner class of Server, no class other than Server can instantiate it; only Server can create and hand out instances of it. Server's getClient() method demonstrates this point: In it, a ShakeRattleAndRoll instance is embedded in the Client that Server creates. Developers can build another VirtualMethod implementation, and pass this other VirtualMethod type into a Client constructor. But the other implementation cannot see inside Server (unless it is an extension of ShakeRattleAndRoll).

What's wrong with this picture?

Nothing prevents a Client from giving its embedded ShakeRattleAndRoll object to another class's instance. Because VirtualMethod and its call() method have public access, the other class could refer to this ShakeRattleAndRoll as a VirtualMethod, just as Client does. The other class would therefore have the same access to ShakeRattleAndRoll's call() method that Client has. At first, this appears to be a flaw in the Access Control pattern. Careful consideration, however, reveals that this is not the case.

Essentially, the code example shows how Server's private method, shakeRattleAndRoll(), can transport to Client. The Client's ShakeRattleAndRoll object is every bit as private as the Server's shakeRattleAndRoll() method! Of course, I've chosen to expose the Client's ShakeRattleAndRoll object via the public method demonstrateAccess(), but that was only to demonstrate that Client could access Server's private method.

This is a very important point so let's view it from a different perspective. Should the developer of Client class be allowed to determine which Client methods are public and which are private? If the Client developer doesn't have that right, then who does? When I designed the Server class, I decided to trust the Client developer with ShakeRattleAndRoll's visibility. If I didn't trust that developer with that responsibility, then I should have handled the Client class myself (which I probably would have if I had decided to make Client an inner class of Server).

We must address this fundamental issue in any security problem. If you give a friend the password to your bank account, how can you be sure that your friend won't give the password to someone you don't trust? There is a simple answer to this perplexing question: if this is a concern, then the first person you don't trust must be your friend!

Still don't trust that Client developer? Check out the pattern's improved version in the next section; it offers tighter access control.

Flexibility and extensibility

The Access Control pattern is flexible and extensible mainly because it allows Server and Client to be independently subclassed. If necessary, Server descendents can override getClient(), returning specialized Client types. The assumption here is that Client descendents won't need increased access to hidden Server methods. This assumption holds for many design problems. The Connection and ConnectionPool example presented above is a good case in point. There are times, however, when you may need to increase Client's ability to see into Server. This isn't difficult to do, though it gets messy when using the pattern as presented above.

The problem is that even a Server subclass cannot see Server's private methods. Thus, you must override (essentially cut and paste) any private methods of the parent that must also be visible in the child. The example below clarifies this point:

// ********* ExtendedServer ********
public class ExtendedServer extends Server {
   
    private void shakeRattleAndRoll() {
        System.out.println("SHAKE ... RATTLE AN' ROLL!!!");
    }
    private void getUpAndBoogy() {
        System.out.println("GET UP ... AND BOOOOOOOGGGYYYYY!!!");
    }
    private class ShakeRattleAndRoll extends VirtualMethod {
        public void call() {
            shakeRattleAndRoll();
        }
    }
    private class GetUpAndBoogy extends VirtualMethod {
        public void call() {
            getUpAndBoogy();
        }
    }
    public ExtendedClient getClient() {
        return new Client(new ShakeRattleAndRoll(), new ShakeRattleAndRoll());
    }
}
// ********* ExtendedClient ********
public class ExtendedClient extends Client {
    private VirtualMethod shakeRattleAndRoll, getUpAndBoogy;
    ExtendedClient(VirtualMethod shakeRattleAndRoll, VirtualMethod
getUpAndBoogy) {
        this.shakeRattleAndRoll = shakeRattleAndRoll;
        this.getUpAndBoogy = getUpAndBoogy;
    }
    public void demonstrateAccess() {
        shakeRattleAndRoll.call();
        getUpAndBoogy.call();
    }
}
1 2 Page 1